Compare commits

...
Author SHA1 Message Date
Pascal Precht fe67997696 feat(StatusInput): add support for asynchronous validators
This adds support for asynchronous validators which are also just `StatusValidator`.

There are a few differences to syncronous validators though:

1. `validate` function doesn't return, it's only used to trigger the async
   validation. Ideally it would return an async object like Promise or Observable
   but since we have to rely on QML `Connections`, this won't work.
2. As mentioned, `Connections` are needed to listen to async events so the
  validation can be resolved.
3. Validation has to be resolved by calling `input.updateValidity` inside the validator.
   That API notifies `StatusInput` that validation is has been performed and expects
   the name of the validator as well as either a boolean (true), if validation was successful,
   or, if validation has failed, a boolean (false) or an error object.

Here's a `StatusENSValidator` as an example:

```qml
StatusValidator {

    id: root

    name: "ensValidator"
    errorMessage: "Couldn't resolve ENS name."

    readonly property string uuid: Utils.uuid()

    property int debounceTime: 600

    signal ensResolved(string address)

    validate: Backpressure.debounce(root, root.debounceTime, function (name) {
        name = name.startsWith("@") ? name.substring(1) : name
        walletModel.ensView.resolveENS(name, uuid)
    })

    Connections {
        target: walletModel.ensView
        onEnsWasResolved: {
            if (uuid !== root.uuid) {
                return
            }
            root.ensResolved(resolvedAddress)
            input.updateValidityAndPendingState(root.name, resolvedAddress !== "")
        }
    }
}
```

Closes #395
2021-09-16 15:58:00 +10:00
Pascal Precht 899f4461bb feat(StatusInput): introduce leftPadding and rightPadding properties 2021-09-16 15:58:00 +10:00
Pascal Precht 4b9d11d1d4 feat(StatusInput): introduce reset API
This can be used to reset text, error message and validity state of inputs
2021-09-16 15:58:00 +10:00
Alexandra Betouni 6e8a36be86 fix(StatusChatInfoToolBar): Right anchored title in StatusChatInfoToolBar
So that elide mode in text can be activated
2021-09-15 11:39:09 +02:00
B.Melnik c65f80d22e feat(Spellchecker): Add Spellchecker class
Closes: #399
2021-09-14 14:34:25 +02:00
B.Melnik f9457ef311 feat(Spellchecking): Add huspell dependency
Closes: #397
2021-09-14 14:33:53 +02:00
Pascal Precht 1374c1933f fix(StatusQ.Controls.Validators): fix bug that addressOrEns validator isn't properly exposed in QML 2021-09-14 14:33:34 +02:00
Khushboo Mehta 1f244f6282 feat(StatusExpandableItem): Correct placement of expandable region in tertiary type 2021-09-14 14:18:59 +02:00
Pascal Precht 80e5b338fd chore: cut 0.9.0 release 2021-09-13 10:59:02 +02:00
Pascal Precht 019471c804 feat(StatusBaseInput): introduce component property
This property enables users to load any component into the input field.
This is useful for rendering a "clearable" icon button, simple icons or
even more complex buttons.

Usage:

```qml
StatusBaseInput {
    ...
    component: StatusIcon {
        name: "cancel"
        color: Theme.palette.dangerColor1
        width: 16
    }
}
```

The `clearable` property of `StatusBaseInput` also renders and icon button
on the right hand side. With this new feature, `clearable` is just a short-hand
for:

```qml
StatusBaseInput {
    ...
    component: StatusFlatRoundButton {
        visible: edit.text.length != 0 &&
                statusBaseInput.clearable &&
                !statusBaseInput.multiline &&
                edit.activeFocus
        type: StatusFlatRoundButton.Type.Secondary
        width: 24
        height: 24
        icon.name: "clear"
        icon.width: 16
        icon.height: 16
        icon.color: Theme.palette.baseColor1
        onClicked: {
            edit.clear()
        }
    }
}
```

Closes #380
2021-09-13 09:56:24 +02:00
Pascal Precht 88fb57dd3b fix(StatusCheckbox): give checkbox label proper theme color 2021-09-13 09:55:54 +02:00
Pascal Precht a93ef16143 feat(StatusQ.Controls.Validators): introduce StatusAddressAndEnsValidator 2021-09-13 09:55:09 +02:00
Pascal Precht 77d0e9b8fd feat(StatusQ.Controls.Validators): introduce StatusAddressValidator 2021-09-13 09:55:09 +02:00
Pascal Precht d73a158418 feat(StatusInput): introduce ValidationMode
This allows users to configure how validation is run. There are two modes:

1. `ValidationMode.OnlyWhenDirty`
2. `ValidationMode.Always`

By default, validation happens when the inputs value changes, or on
initial `Component.onCompleted` event. The first mode allows for not
performing validation when the input field is blank and validation.
isn't necessary (yet).
2021-09-13 09:55:09 +02:00
Pascal Precht 1a23cc1912 feat(StatusValidator): allow validators to provide default errorMessage
Validators can now define a default `errorMessage` like so:

```qml
StatusValidator {
  ...
  errorMessage: "..."
}
```

Because there's no access to runtime validation errors, `errorMessage` have to
be static. However, if applications wish to provide their own `errorMessage`
they can still override it and make it dynamic:

```qml
SomeValidator {
  ...
  errorMessage: input.errors.someValidator ? "Whoopsie" : ""
}
```
2021-09-13 09:55:09 +02:00
Pascal Precht 48309d3040 Revert "chore: replace profile icon (#312)"
This reverts commit aab59763e5.
2021-09-09 11:37:45 +02:00
Khushboo Mehta 718171fd7b feat(StatusExpandableItem): Refactored the StatusExpandableSettingsItem to support different types
Renamed StatusExpandableSettingsItem to StatusExpandableItem.
Added support for dofferent types of styles for the item.
Type Primary: Relates to Settings Design
Type Secondary: Relates to Collectibles Design
Type Tertiary: Relates to the Collectibles detailed view design

BREAKING CHANGE: Renamed and expanded features of the  StatusExpandableSettingsItem to StatusExpandableItem
2021-09-08 13:37:03 +02:00
Pascal Precht efe3116610 feat(StatusSearchPopup): introduce forceActiveFocus API
This is used to enforce focus on the search input when the popup
is opened. In fact users decide to override the search popup's
`onOpened` handler, they still get access to this API to make use of it.
2021-09-08 12:36:31 +02:00
Pascal Precht aab44c1e0d feat(Status.Core.Theme): add RobotoMono font
Closes #342
2021-09-08 11:28:07 +02:00
Pascal Precht eabc62f796 fix(StatusModal): don't reserve header subtitle space
Closes #378
2021-09-08 11:21:18 +02:00
Pascal Precht 2971c60737 fix(StatusChatListAndCategories): rely on correct tooltip settings prop
Category tooltip settings were broken in this component due to a mismatching
property name.

This commit fixes it.
2021-09-08 11:19:54 +02:00
B.Melnik 29e9557d5f fix(StatusAppThreePanelLayout): Fix margin between left and center panels
This margin shows because of `handle` width incuded in `SplitView` width. Handle have `Component` type and can't accessable for getting sizes. I add `magic constant` as hotfix.

Closes: #307
2021-09-08 11:19:36 +02:00
B.Melnik 89f54a85c8 refactor(StatusPopupMenu): Refactor bug with reopen menu 2021-09-08 11:18:16 +02:00
Alexandra Betouni 73c77c29c2 feat(StatusInput): exposed edit component
We need to be able to call forceActiveFocus, so
TextEdit component should be somehow accessible

Relates to desktop #3310
2021-09-06 16:51:47 +02:00
Pascal Precht 62e93ecffb chore: release v0.8.0 2021-09-06 14:54:58 +02:00
Pascal Precht ddfca7a8fa feat(StatusBaseInput): introduce focussed property
We can't alias the property as `focus` because this is a final readonly
property, so we're introducing a `focussed` property instead.

Closes #373
2021-09-06 14:53:15 +02:00
B.Melnik 3187de5449 fix(StatusModal): Remove self-calculating height
Based on Qt docs `contentItem` must have `implicit` sizes.
link to rules: https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#popup-sizing
2021-09-06 14:51:23 +02:00
Alexandra Betouni d648230d79 feat(StatusInput): Introduced secondaryLabel property
Due to design updates in AddAccount modal, updates are
needed in StatusBaseInput and StatusInput

* Added the possibility of having the icon on the right
  side
* Added secondaryLabel for title
* Added examples in StatusInputPage

Closes #383
2021-09-06 14:50:51 +02:00
Khushboo Mehta 38fb8f61a5 feat(qrc): Add new icon needed for share modal 2021-09-06 11:01:13 +02:00
Khushboo Mehta 8a94fb5412 feat(StatusModal): Add popup menu support for StatusModal
Added logic to support a popup menu to be launched from the StatusModal header.
Added an example in sandbox to demonstrate its usage.

fixes #374
2021-09-06 11:01:13 +02:00
B.Melnik 061c7d1c90 fix(StatusInput): Forward keys events to root
Closes: #372
2021-09-03 12:33:58 +02:00
Khushboo Mehta f3ab4ce9c9 feat(StatusExpandableSettingsItem): Added new component for wallet settings
Also added a page in the sandbox to demonstrate its usage.
2021-09-03 10:55:27 +02:00
Pascal Precht d449f0e903 feat(StatusQ.Controls): introduce StatusSwitchTabBar and StatusSwitchTabButton
This commit adds two new components to the StatusQ.Controls module:

- StatusSwitchTabBar
- StatusSwitchTabButton

Usage:

```qml
StatusSwitchTabBar {

    StatusSwitchTabButton {
        text: "Tab 1"
    }

    StatusSwitchTabButton {
        text: "Tab 2"
    }

    StatusSwitchTabButton {
        text: "Tab 3"
    }
}
```

Closes #365
2021-09-03 10:45:45 +02:00
B.Melnik 4a6800ed77 refactor(StatusModal): Remove custom content property
BREAKING CHANGES:
- `content` property removed
- `Loader` inside StatusModal removed
- default `contentItem` property should be used now

Closes: #306
2021-09-02 11:06:32 +02:00
B.Melnik d64aa6deed fix(StatusBaseInput): fix one line scroll
Closes: #291
2021-09-01 12:51:06 +02:00
B.Melnik 64098e84d3 feat(StatusChatListAndCategories): Add tooltip settings for categories buttons
Closes: #226
2021-09-01 12:43:47 +02:00
57 changed files with 1386 additions and 225 deletions
+63
View File
@@ -1,3 +1,66 @@
<a name=""></a>
## v0.9.0 (2021-09-13)
#### Breaking Changes
* **StatusExpandableItem:** Refactored the StatusExpandableSettingsItem to support different types ([718171fd](718171fd))
#### Bug Fixes
* **StatusAppThreePanelLayout:** Fix margin between left and center panels ([29e9557d](29e9557d))
* **StatusChatListAndCategories:** rely on correct tooltip settings prop ([2971c607](2971c607))
* **StatusCheckbox:** give checkbox label proper theme color ([88fb57dd](88fb57dd))
* **StatusModal:** don't reserve header subtitle space ([eabc62f7](eabc62f7), closes [#378](378))
#### Features
* **Status.Core.Theme:** add RobotoMono font ([aab44c1e](aab44c1e), closes [#342](342))
* **StatusBaseInput:** introduce `component` property ([019471c8](019471c8), closes [#380](380))
* **StatusExpandableItem:** Refactored the StatusExpandableSettingsItem to support different types ([718171fd](718171fd))
* **StatusInput:**
* introduce `ValidationMode` ([d73a1584](d73a1584))
* exposed edit component ([73c77c29](73c77c29))
* **StatusQ.Controls.Validators:**
* introduce `StatusAddressAndEnsValidator` ([a93ef161](a93ef161))
* introduce `StatusAddressValidator` ([77d0e9b8](77d0e9b8))
* **StatusSearchPopup:** introduce forceActiveFocus API ([efe31166](efe31166))
* **StatusValidator:** allow validators to provide default `errorMessage` ([1a23cc19](1a23cc19))
<a name=""></a>
## v0.8.0 (2021-09-06)
#### Bug Fixes
* **StatusBaseInput:**
* fix one line scroll ([d64aa6de](d64aa6de))
* Make clear button bigger ([387bfe77](387bfe77))
* **StatusChatToolBar:** Fix mouse event catching after menu closing ([fbecac4a](fbecac4a))
* **StatusInput:** Forward keys events to root ([061c7d1c](061c7d1c))
* **StatusListItem:** Add propogateCompostedEvents to title mouse area ([5c706fdc](5c706fdc))
* **StatusModal:** Remove self-calculating height ([3187de54](3187de54))
#### Features
* introduce bigger versions of navbar icons ([0a4d3860](0a4d3860))
* **StatusBaseInput:** introduce focussed property ([ddfca7a8](ddfca7a8), closes [#373](373))
* **StatusChatListAndCategories:** Add tooltip settings for categories buttons ([64098e84](64098e84))
* **StatusDescriptionListItem:**
* expose subtitle component for fine control ([1749cc0e](1749cc0e))
* introduce support for `value` ([a963ef80](a963ef80))
* **StatusExpandableSettingsItem:** Added new component for wallet settings ([f3ab4ce9](f3ab4ce9))
* **StatusInput:** Introduced secondaryLabel property ([d648230d](d648230d), closes [#383](383))
* **StatusModal:**
* Add popup menu support for StatusModal ([8a94fb54](8a94fb54))
* add ability to set elide config of header titles ([28e514f9](28e514f9), closes [#353](353))
* **StatusQ.Controls:** introduce StatusSwitchTabBar and StatusSwitchTabButton ([d449f0e9](d449f0e9), closes [#365](365))
* **StatusSearchPopup:** add function hook to allow timestamp formatting ([b45aba4b](b45aba4b), closes [#363](363))
* **qrc:** Add new icon needed for share modal ([38fb8f61](38fb8f61))
<a name=""></a>
## v0.7.0 (2021-08-30)
+2 -2
View File
@@ -752,7 +752,7 @@ Rectangle {
icon.name: "notification"
}
content: StatusBaseText {
contentItem: StatusBaseText {
anchors.centerIn: parent
text: "Contact request will be shown here"
font.pixelSize: 15
@@ -781,7 +781,7 @@ Rectangle {
header.subTitle: "Public Community"
header.image.source: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
content: Column {
contentItem: Column {
width: demoCommunityDetailModal.width
StatusModalDivider {
+54 -12
View File
@@ -13,6 +13,11 @@ Column {
onClicked: simpleModal.open()
}
StatusButton {
text: "Simple title modal"
onClicked: simpleTitleModal.open()
}
StatusButton {
text: "Modal with header image"
onClicked: headerImageModal.open()
@@ -53,6 +58,11 @@ Column {
onClicked: modalWithLongTitles.open()
}
StatusButton {
text: "Modal with Header Popup Menu"
onClicked: modalWithHeaderPopupMenu.open()
}
StatusModal {
id: simpleModal
anchors.centerIn: parent
@@ -60,6 +70,12 @@ Column {
header.subTitle: "Subtitle"
}
StatusModal {
id: simpleTitleModal
anchors.centerIn: parent
header.title: "Some Title"
}
StatusModal {
id: headerImageModal
anchors.centerIn: parent
@@ -145,7 +161,7 @@ Column {
}
]
content: StatusBaseText {
contentItem: StatusBaseText {
anchors.centerIn: parent
text: "Some text content"
font.pixelSize: 15
@@ -169,7 +185,7 @@ Column {
header.title: "Header"
header.subTitle: "SubTitle"
content: StatusBaseText {
contentItem: StatusBaseText {
id: text
anchors.centerIn: parent
text: "Some text content"
@@ -181,7 +197,7 @@ Column {
StatusButton {
text: "Change text"
onClicked: {
modalWithContentAccess.contentComponent.text = "Changed!"
modalWithContentAccess.contentItem.text = "Changed!"
}
}
]
@@ -195,8 +211,7 @@ Column {
header.icon.isLetterIdenticon: true
header.icon.background.color: "red"
content: StatusBaseText {
id: text
contentItem: StatusBaseText {
anchors.centerIn: parent
text: "Some text content"
font.pixelSize: 15
@@ -207,7 +222,7 @@ Column {
StatusButton {
text: "Change text"
onClicked: {
modalWithContentAccess.contentComponent.text = "Changed!"
modalWithLetterIdenticon.contentItem.text = "Changed!"
}
}
]
@@ -222,8 +237,7 @@ Column {
CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC"
header.image.isIdenticon: true
content: StatusBaseText {
id: text
contentItem: StatusBaseText {
anchors.centerIn: parent
text: "Some text content"
font.pixelSize: 15
@@ -234,7 +248,7 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
StatusButton {
text: "Change text"
onClicked: {
modalWithContentAccess.contentComponent.text = "Changed!"
modalWithIdenticon.contentItem.text = "Changed!"
}
}
]
@@ -250,8 +264,7 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC"
header.image.isIdenticon: true
content: StatusBaseText {
id: text
contentItem: StatusBaseText {
anchors.centerIn: parent
text: "Some text content"
font.pixelSize: 15
@@ -262,9 +275,38 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
StatusButton {
text: "Change text"
onClicked: {
modalWithContentAccess.contentComponent.text = "Changed!"
modalWithLongTitles.contentItem.text = "Changed!"
}
}
]
}
StatusModal {
id: modalWithHeaderPopupMenu
anchors.centerIn: parent
header.title: "helloworld.eth"
header.subTitle: "Basic address"
header.popupMenu: StatusPopupMenu {
id: popupMenu
Repeater {
model: dummyAccountsModel
delegate: Loader {
sourceComponent: popupMenu.delegate
onLoaded: {
item.action.text = model.name
item.action.iconSettings.name = model.iconName
}
}
}
onMenuItemClicked: {
popupMenu.dismiss()
}
}
}
ListModel {
id: dummyAccountsModel
ListElement{name: "Account 1"; iconName: "filled-account"}
ListElement{name: "Account 2"; iconName: "filled-account"}
}
}
@@ -0,0 +1,152 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
Column {
spacing: 12
width: 800
anchors.top:parent.top
leftPadding: 20
rightPadding: 20
Rectangle {
width: parent.width
height: 30
color: Theme.palette.baseColor2
StatusBaseText {
anchors.verticalCenter: parent.verticalCenter
text: "Type Primary"
color: Theme.palette.directColor1
}
}
StatusExpandableItem {
anchors.horizontalCenter: parent.horizontalCenter
expandable: false
icon.name: "seed-phrase"
primaryText: "Back up seed phrase"
secondaryText: "Back up your seed phrase now to secure this account ajhaDH SDHSAHDLSADBSA,DLISAHDLASD ADASDHASLDHALSDHAS DAS,DASJDGLIASGD"
button.text: qsTr("Back up seed phrase")
}
StatusExpandableItem {
anchors.horizontalCenter: parent.horizontalCenter
expandable: true
icon.name: "secret"
primaryText: "Account signing phrase"
secondaryText: "View your signing phrase and ensure that you never get scammed. View your signing phrase and ensure that you never get scammed."
expandableComponent: notImplemented
}
StatusExpandableItem {
anchors.horizontalCenter: parent.horizontalCenter
expandable: true
icon.name: "seed-phrase"
primaryText: "View private key"
secondaryText: "Back up your seed phrase now to secure this account"
expandableComponent: notImplemented
button.text: "View private key"
button.icon.name: "tiny/public-chat"
button.onClicked: {
// To-do open enter password Modal
expanded = !expanded
}
}
Rectangle {
width: parent.width
height: 30
color: Theme.palette.baseColor2
StatusBaseText {
anchors.verticalCenter: parent.verticalCenter
text: "Type Secondary"
color: Theme.palette.directColor1
}
}
StatusExpandableItem {
anchors.horizontalCenter: parent.horizontalCenter
type: StatusExpandableItem.Type.Secondary
expandable: true
image.source: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
primaryText: "CryptoKitties"
additionalText: "1456 USD"
expandableComponent: notImplemented
}
StatusExpandableItem {
anchors.horizontalCenter: parent.horizontalCenter
type: StatusExpandableItem.Type.Secondary
expandable: true
image.source: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
primaryText: "Adding Really long text to test scenario of having very long text along with tertiary text"
additionalText: "564.90 USD"
expandableComponent: notImplemented
}
StatusExpandableItem {
anchors.horizontalCenter: parent.horizontalCenter
type: StatusExpandableItem.Type.Secondary
expandable: true
primaryText: "CryptoKitties"
additionalText: "1456 USD"
expandableComponent: notImplemented
}
Rectangle {
width: parent.width
height: 30
color: Theme.palette.baseColor2
StatusBaseText {
anchors.verticalCenter: parent.verticalCenter
text: "Type Tertiary"
color: Theme.palette.directColor1
}
}
StatusExpandableItem {
anchors.horizontalCenter: parent.horizontalCenter
type: StatusExpandableItem.Type.Tertiary
expandable: true
primaryText: "CryptoKitties"
expandableComponent: notImplemented
}
StatusExpandableItem {
anchors.horizontalCenter: parent.horizontalCenter
type: StatusExpandableItem.Type.Tertiary
expandable: true
primaryText: "Rescue Moon"
expandableComponent: notImplemented
}
Component {
id: notImplemented
Rectangle {
anchors.centerIn: parent
width: 654
height: infoText.implicitHeight
color: Theme.palette.baseColor5
StatusBaseText {
id: infoText
anchors.centerIn: parent
color: Theme.palette.directColor4
font.pixelSize: 15
lineHeight: 22
lineHeightMode: Text.FixedHeight
font.weight: Font.Medium
text: qsTr("Not Implemented")
}
}
}
}
+36 -6
View File
@@ -68,21 +68,51 @@ Column {
}
StatusInput {
label: "StatusInput"
secondaryLabel: "with right icon"
input.icon.width: 15
input.icon.height: 11
input.icon.name: text !== "" ? "checkmark" : ""
input.leftIcon: false
}
StatusInput {
label: "Label"
secondaryLabel: "secondary label"
input.placeholderText: "Placeholder"
input.implicitHeight: 56
}
StatusInput {
id: input
label: "Label"
charLimit: 30
input.placeholderText: "Input with validator"
validators: [
StatusMinLengthValidator { minLength: 10 }
]
onTextChanged: {
if (errors && errors.minLength) {
errorMessage = `Value can't be shorter than ${errors.minLength.min} but got ${errors.minLength.actual}`
StatusMinLengthValidator {
minLength: 10
errorMessage: {
if (input.errors && input.errors.minLength) {
return `Value can't be shorter than ${input.errors.minLength.min} but got ${input.errors.minLength.actual}`
}
return ""
}
}
]
}
StatusInput {
label: "Label"
input.placeholderText: "Input width component (right side)"
input.component: StatusIcon {
icon: "cancel"
height: 16
color: Theme.palette.dangerColor1
}
}
StatusInput {
input.multiline: true
input.placeholderText: "Multiline"
+24
View File
@@ -0,0 +1,24 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.13
import StatusQ.Controls 0.1
GridLayout {
columns: 1
columnSpacing: 5
rowSpacing: 5
StatusSwitchTabBar {
StatusSwitchTabButton {
text: "Swap"
}
StatusSwitchTabButton {
text: "Swap & Send"
}
StatusSwitchTabButton {
text: "Send"
}
}
}
+20
View File
@@ -138,6 +138,11 @@ StatusWindow {
selected: page.sourceComponent == buttonsComponent
onClicked: page.sourceComponent = buttonsComponent
}
StatusNavigationListItem {
title: "StatusSwitchTab"
selected: page.sourceComponent == statusTabSwitchesComponent
onClicked: page.sourceComponent = statusTabSwitchesComponent
}
StatusNavigationListItem {
title: "Controls"
selected: page.sourceComponent == controlsComponent
@@ -164,6 +169,11 @@ StatusWindow {
selected: page.sourceComponent == othersComponent
onClicked: page.sourceComponent = othersComponent
}
StatusNavigationListItem {
title: "StatusExpandableItem"
selected: page.sourceComponent == settingsComponent
onClicked: page.sourceComponent = settingsComponent
}
StatusListSectionHeadline { text: "StatusQ.Popup" }
StatusNavigationListItem {
title: "StatusPopupMenu"
@@ -281,6 +291,16 @@ StatusWindow {
Popups {}
}
Component {
id: statusTabSwitchesComponent
StatusTabSwitchPage {}
}
Component {
id: settingsComponent
StatusExpandableSettingsItemPage{}
}
Component {
id: demoAppCmp
+1
View File
@@ -13,5 +13,6 @@
<file>ThemeSwitch.qml</file>
<file>Layout.qml</file>
<file>Popups.qml</file>
<file>StatusExpandableSettingsItemPage.qml</file>
</qresource>
</RCC>
+12 -2
View File
@@ -12,7 +12,8 @@ DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
handler.cpp \
main.cpp \
sandboxapp.cpp
sandboxapp.cpp \
spellchecker.cpp
!macx {
SOURCES += statuswindow.cpp
@@ -24,6 +25,14 @@ macx {
CONFIG -= app_bundle
OBJECTIVE_SOURCES += \
statuswindow_mac.mm
hunspellTarget.depends = FORCE
hunspellTarget.commands = brew install hunspell
QMAKE_EXTRA_TARGETS += hunspellTarget
LIBS += -L"/usr/local/lib" -lhunspell-1.7
INCLUDEPATH += /usr/local/include/hunspell
}
ios {
@@ -51,7 +60,8 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
HEADERS += \
handler.h \
sandboxapp.h \
statuswindow.h
statuswindow.h \
spellchecker.h
OTHER_FILES += $$files($$PWD/../*.qml, true)
OTHER_FILES += $$files($$PWD/*.qml, true)
+2
View File
@@ -5,6 +5,7 @@
#include <QDebug>
#include "statuswindow.h"
#include "spellchecker.h"
SandboxApp::SandboxApp(int &argc, char **argv)
: QGuiApplication(argc, argv),
@@ -16,6 +17,7 @@ SandboxApp::SandboxApp(int &argc, char **argv)
void SandboxApp::startEngine()
{
qmlRegisterType<StatusWindow>("Sandbox", 0, 1, "StatusWindow");
qmlRegisterType<SpellChecker>("Sandbox", 0, 1, "Spellchecker");
#ifdef QT_DEBUG
const QUrl url(applicationDirPath() + "/../main.qml");
+174
View File
@@ -0,0 +1,174 @@
#include "spellchecker.h"
#include "hunspell.hxx"
#include <QTextCodec>
#include <QFile>
#include <QDebug>
#include <QLocale>
#include <QRegularExpression>
#include <QApplication>
#include <QDir>
SpellChecker::SpellChecker(QObject *parent)
: QObject(parent)
, m_hunspell(nullptr)
, m_userDict("userDict_")
{
}
SpellChecker::~SpellChecker()
{
#ifdef Q_OS_MACOS
delete m_hunspell;
#endif
}
bool SpellChecker::spell(const QString &word)
{
#ifdef Q_OS_MACOS
return m_hunspell->spell(m_codec->fromUnicode(word).toStdString());
#else
return true;
#endif
}
bool SpellChecker::isInit() const
{
return !m_hunspell;
}
void SpellChecker::initHunspell()
{
#ifdef Q_OS_MACOS
if (m_hunspell) {
delete m_hunspell;
}
QString dictFile = QApplication::applicationDirPath() + "/dictionaries/" + m_lang + "/index.dic";
QString affixFile = QApplication::applicationDirPath() + "/dictionaries/" + m_lang + "/index.aff";
QByteArray dictFilePathBA = dictFile.toLocal8Bit();
QByteArray affixFilePathBA = affixFile.toLocal8Bit();
m_hunspell = new Hunspell(affixFilePathBA.constData(),
dictFilePathBA.constData());
// detect encoding analyzing the SET option in the affix file
auto encoding = QStringLiteral("ISO8859-15");
QFile _affixFile(affixFile);
if (_affixFile.open(QIODevice::ReadOnly)) {
QTextStream stream(&_affixFile);
QRegularExpression enc_detector(
QStringLiteral("^\\s*SET\\s+([A-Z0-9\\-]+)\\s*"),
QRegularExpression::CaseInsensitiveOption);
QString sLine;
QRegularExpressionMatch match;
while (!stream.atEnd()) {
sLine = stream.readLine();
if (sLine.isEmpty()) { continue; }
match = enc_detector.match(sLine);
if (match.hasMatch()) {
encoding = match.captured(1);
qDebug() << "Encoding set to " + encoding;
break;
}
}
_affixFile.close();
}
m_codec = QTextCodec::codecForName(encoding.toLatin1().constData());
QString userDict = m_userDict + m_lang + ".txt";
if (!userDict.isEmpty()) {
QFile userDictonaryFile(userDict);
if (userDictonaryFile.open(QIODevice::ReadOnly)) {
QTextStream stream(&userDictonaryFile);
for (QString word = stream.readLine();
!word.isEmpty();
word = stream.readLine())
ignoreWord(word);
userDictonaryFile.close();
} else {
qWarning() << "User dictionary in " << userDict
<< "could not be opened";
}
} else {
qDebug() << "User dictionary not set.";
}
#endif
}
QVariantList SpellChecker::suggest(const QString &word)
{
int numSuggestions = 0;
QVariantList suggestions;
#ifdef Q_OS_MACOS
std::vector<std::string> wordlist;
wordlist = m_hunspell->suggest(m_codec->fromUnicode(word).toStdString());
numSuggestions = static_cast<int>(wordlist.size());
if (numSuggestions > 0) {
suggestions.reserve(numSuggestions);
for (int i = 0; i < numSuggestions; i++) {
suggestions << m_codec->toUnicode(
QByteArray::fromStdString(wordlist[i]));
}
}
#endif
return suggestions;
}
void SpellChecker::ignoreWord(const QString &word)
{
#ifdef Q_OS_MACOS
m_hunspell->add(m_codec->fromUnicode(word).constData());
#endif
}
void SpellChecker::addToUserWordlist(const QString &word)
{
#ifdef Q_OS_MACOS
QString userDict = m_userDict + m_lang + ".txt";
if (!userDict.isEmpty()) {
QFile userDictonaryFile(userDict);
if (userDictonaryFile.open(QIODevice::Append)) {
QTextStream stream(&userDictonaryFile);
stream << word << "\n";
userDictonaryFile.close();
} else {
qWarning() << "User dictionary in " << userDict
<< "could not be opened for appending a new word";
}
} else {
qDebug() << "User dictionary not set.";
}
#endif
}
const QString& SpellChecker::lang() const
{
return m_lang;
}
void SpellChecker::setLang(const QString& lang)
{
if (m_lang != lang) {
m_lang = lang;
initHunspell();
emit langChanged();
}
}
const QString& SpellChecker::userDict() const
{
return m_userDict;
}
void SpellChecker::setUserDict(const QString& userDict)
{
if (m_userDict != userDict) {
m_userDict = userDict;
emit userDictChanged();
}
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef SPELLCHECKER_H
#define SPELLCHECKER_H
#include <QObject>
#include <QVariant>
#include <QQuickTextDocument>
#include <QSyntaxHighlighter>
#ifdef Q_OS_MACOS
class Hunspell;
#endif
class QTextCodec;
class SpellChecker : public QObject
{
Q_OBJECT
Q_PROPERTY(QString lang READ lang WRITE setLang NOTIFY langChanged)
Q_PROPERTY(QString userDict READ userDict WRITE setUserDict NOTIFY userDictChanged)
public:
explicit SpellChecker(QObject *parent = nullptr);
~SpellChecker();
Q_INVOKABLE bool spell(const QString& word);
Q_INVOKABLE QVariantList suggest(const QString &word);
Q_INVOKABLE void ignoreWord(const QString &word);
Q_INVOKABLE void addToUserWordlist(const QString &word);
Q_INVOKABLE bool isInit() const;
const QString& lang() const;
void setLang(const QString& lang);
const QString& userDict() const;
void setUserDict(const QString& userDict);
signals:
void langChanged();
void userDictChanged();
private:
void initHunspell();
private:
QString m_lang;
QString m_userDict;
QQuickTextDocument *m_document;
#ifdef Q_OS_MACOS
Hunspell *m_hunspell;
#endif
QTextCodec *m_codec;
};
#endif // SPELLCHECKER_H
@@ -28,6 +28,8 @@ Item {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 5
anchors.right: (implicitWidth > parent.width - 50) ? statusMenuButton.left : undefined
anchors.rightMargin: 5
type: StatusChatInfoButton.Type.OneToOneChat
onClicked: statusChatInfoToolBar.chatInfoButtonClicked()
}
@@ -39,7 +41,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
visible: popupMenuSlot.active
width: 32
width: visible ? 32 : 0
height: 32
type: StatusRoundButton.Type.Secondary
@@ -5,6 +5,7 @@ import QtQuick.Controls 2.14 as QC
import StatusQ.Core.Utils 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import StatusQ.Core 0.1
Item {
id: statusChatListAndCategories
@@ -12,6 +13,13 @@ Item {
implicitHeight: chatListsAndCategories.height
implicitWidth: chatListsAndCategories.width
property StatusTooltipSettings categoryAddButtonToolTip: StatusTooltipSettings {
text: "Add channel inside category"
}
property StatusTooltipSettings categoryMenuButtonToolTip: StatusTooltipSettings {
text: "More"
}
property string selectedChatId: ""
property bool showCategoryActionButtons: false
property bool showPopupMenu: true
@@ -121,6 +129,9 @@ Item {
}
}
addButton.tooltip: statusChatListAndCategories.categoryAddButtonToolTip
menuButton.tooltip: statusChatListAndCategories.categoryMenuButtonToolTip
originalOrder: model.position
categoryId: model.categoryId
name: model.name
+4 -17
View File
@@ -93,19 +93,8 @@ Rectangle {
property bool showMoreMenu: false
onClicked: {
if (!showMoreMenu) {
popupMenuSlot.item.popup(-popupMenuSlot.item.width + menuButton.width, menuButton.height + 4)
statusChatToolBar.menuButtonClicked()
}
}
Timer {
id: menuClosedUpdater
interval: 100
repeat: false
onTriggered: {
menuButton.showMoreMenu = false
}
popupMenuSlot.item.popup(-popupMenuSlot.item.width + menuButton.width, menuButton.height + 4)
statusChatToolBar.menuButtonClicked()
}
Loader {
@@ -114,15 +103,13 @@ Rectangle {
onLoaded: {
popupMenuSlot.item.closeHandler = function () {
menuButton.highlighted = false
menuClosedUpdater.start()
}
popupMenuSlot.item.openHandler = function () {
menuButton.highlighted = true
menuButton.showMoreMenu = true
}
}
}
}
}
Rectangle {
@@ -131,7 +118,7 @@ Rectangle {
color: Theme.palette.directColor7
anchors.verticalCenter: parent.verticalCenter
visible: notificationButton.visible &&
(menuButton.visible || membersButton.visible || searchButton.visible)
(menuButton.visible || membersButton.visible || searchButton.visible)
}
StatusFlatRoundButton {
@@ -0,0 +1,229 @@
import QtQuick 2.14
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
Rectangle {
id: statusExpandableItem
property alias primaryText: primaryText.text
property alias secondaryText: secondaryText.text
property alias additionalText: additionalText.text
property alias button: button
property alias expandableComponent: expandableRegion.sourceComponent
property int type: StatusExpandableItem.Type.Primary
property bool expandable: true
property bool expanded: false
property StatusIconSettings icon: StatusIconSettings {
color: Theme.palette.directColor1
background: StatusIconBackgroundSettings {
width: 32
height: 32
color: Theme.palette.primaryColor2
}
}
property StatusImageSettings image: StatusImageSettings {
width: 40
height: 40
}
enum Type {
Primary, // 0
Secondary, // 1
Tertiary // 2
}
implicitWidth: 718
radius: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? 8 : 0
color: "transparent"
border.color: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? Theme.palette.baseColor2 : "transparent"
state: "COLLAPSED"
clip: true
Rectangle {
id: separatorRect
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
height: 1
color: Theme.palette.baseColor2
visible: (statusExpandableItem.type === StatusExpandableItem.Type.Tertiary)
}
Loader {
id: identicon
anchors.top: parent.top
anchors.topMargin: 25
anchors.left: parent.left
anchors.leftMargin: (statusExpandableItem.type === StatusExpandableItem.Type.Secondary) ? 0 : 11
active: (statusExpandableItem.type !== StatusExpandableItem.Type.Tertiary)
sourceComponent: !!statusExpandableItem.image.source.toString() ? roundedImage :
!!statusExpandableItem.icon.name.toString() ? roundedIcon : letterIdenticon
}
Component {
id: roundedImage
StatusRoundedImage {
image.source: statusExpandableItem.image.source
}
}
Component {
id: roundedIcon
StatusRoundIcon {
icon.background.width: statusExpandableItem.icon.background.width
icon.background.height: statusExpandableItem.icon.background.height
icon.background.color: statusExpandableItem.icon.background.color
icon.color: statusExpandableItem.icon.color
icon.name: statusExpandableItem.icon.name
}
}
Component {
id: letterIdenticon
StatusLetterIdenticon {
height: 40
width: 40
name: primaryText.text
letterSize: 20
color: Theme.palette.miscColor5
}
}
StatusBaseText {
id: primaryText
anchors.top: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ||
(statusExpandableItem.type === StatusExpandableItem.Type.Tertiary) ? parent.top : undefined
anchors.topMargin: (statusExpandableItem.type === StatusExpandableItem.Type.Tertiary) ? 29 : 17
anchors.left: identicon.active ? identicon.right : parent.left
anchors.leftMargin: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? 10 : 16
anchors.verticalCenter: (statusExpandableItem.type === StatusExpandableItem.Type.Secondary) ? identicon.verticalCenter : undefined
width: !!additionalText.text ? (button.visible ? parent.width - icon.background.width - button.width - additionalText.contentWidth - 110 :
parent.width - icon.background.width - additionalText.contentWidth - 110) :
(button.visible ? parent.width - icon.background.width - button.width - 70 :
parent.width - icon.background.width - 70)
font.weight: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? Font.Medium : Font.Normal
font.pixelSize: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? 15 : 17
lineHeight: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? 22 : 24
lineHeightMode: Text.FixedHeight
elide: Text.ElideRight
color: (statusExpandableItem.type === StatusExpandableItem.Type.Tertiary) ? Theme.palette.baseColor1 : Theme.palette.directColor1
}
StatusBaseText {
id: secondaryText
anchors.top: primaryText.bottom
anchors.topMargin: 4
anchors.left: primaryText.left
anchors.right: primaryText.right
font.pixelSize: 15
lineHeight: 22
lineHeightMode: Text.FixedHeight
elide: Text.ElideRight
color: Theme.palette.directColor3
}
StatusBaseText {
id: additionalText
anchors.verticalCenter: primaryText.verticalCenter
anchors.verticalCenterOffset: 2
anchors.right: expandImage.left
anchors.rightMargin: 16
font.pixelSize: 15
lineHeight: 24
lineHeightMode: Text.FixedHeight
elide: Text.ElideRight
color: Theme.palette.baseColor1
}
StatusButton {
id: button
anchors.top: parent.top
anchors.topMargin: 19
anchors.right: parent.right
anchors.rightMargin: 16
visible: !!text
}
StatusIcon {
id: expandImage
anchors.verticalCenter: (statusExpandableItem.type === StatusExpandableItem.Type.Tertiary) ?
primaryText.verticalCenter : identicon.verticalCenter
anchors.verticalCenterOffset:(statusExpandableItem.type === StatusExpandableItem.Type.Tertiary) ? -3 : -1
anchors.right: parent.right
anchors.rightMargin: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? 23 : 6
visible: expandable && !button.visible
color: (statusExpandableItem.type === StatusExpandableItem.Type.Tertiary) ?
Theme.palette.baseColor1 :
Theme.palette.directColor1
}
MouseArea {
anchors.fill: parent
onClicked: {
if(expandable) {
expanded = !expanded
}
}
cursorShape: Qt.PointingHandCursor
visible: !button.visible && expandable
}
Loader {
id: expandableRegion
anchors.top: !!secondaryText.text ? secondaryText.bottom: primaryText.bottom
anchors.topMargin: 16
anchors.left: parent.left
anchors.leftMargin: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? 48 : 0
anchors.right: parent.right
anchors.rightMargin: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? 16 : 0
active: false
}
onExpandedChanged: {
if(expanded) {
state = "EXPANDED"
}
else {
state = "COLLAPSED"
}
}
states: [
State {
name: "EXPANDED"
PropertyChanges {target: expandImage; icon: "chevron-up"}
PropertyChanges {target: statusExpandableItem; height: 82 + expandableRegion.height + 22}
PropertyChanges {target: expandableRegion; active: true}
},
State {
name: "COLLAPSED"
PropertyChanges {target: expandImage; icon: "chevron-down"}
PropertyChanges {target: statusExpandableItem; height: 82}
PropertyChanges {target: expandableRegion; active: false}
}
]
transitions: [
Transition {
from: "COLLAPSED"
to: "EXPANDED"
NumberAnimation { properties: "height"; duration: 200 }
},
Transition {
from: "EXPANDED"
to: "COLLAPSED"
NumberAnimation { properties: "height"; duration: 200 }
}
]
}
+1
View File
@@ -20,3 +20,4 @@ StatusRoundIcon 0.1 StatusRoundIcon.qml
StatusRoundedImage 0.1 StatusRoundedImage.qml
StatusMacWindowButtons 0.1 StatusMacWindowButtons.qml
StatusListItemBadge 0.1 StatusListItemBadge.qml
StatusExpandableItem 0.1 StatusExpandableItem.qml
+175 -138
View File
@@ -22,10 +22,12 @@ Item {
property alias selectionEnd: edit.selectionEnd
property alias cursorPosition: edit.cursorPosition
property alias edit: edit
property alias text: edit.text
property alias color: edit.color
property alias font: edit.font
property alias focussed: edit.activeFocus
property alias verticalAlignmet: edit.verticalAlignment
property alias horizontalAlignment: edit.horizontalAlignment
@@ -45,23 +47,41 @@ Item {
property bool valid: true
property bool pristine: true
property bool dirty: false
property bool pending: false
property bool leftIcon: true
property StatusIconSettings icon: StatusIconSettings {
width: 24
height: 24
name: ""
color: Theme.palette.baseColor1
}
property Item component
onClearableChanged: {
if (clearable && !component) {
clearButtonLoader.active = true
clearButtonLoader.parent = statusBaseInputComponentSlot
} else {
clearButtonLoader.active = false
}
}
onComponentChanged: {
if (!!component) {
component.parent = statusBaseInputComponentSlot
}
}
implicitWidth: 448
implicitHeight: multiline ? Math.max(edit.implicitHeight + topPadding + bottomPadding, 44) : 44
implicitHeight: multiline ? Math.max((edit.implicitHeight + topPadding + bottomPadding), 44) : 44
Rectangle {
width: parent.width
height: maximumHeight != 0 ? Math.min(
minimumHeight != 0 ? Math.max(statusBaseInput.implicitHeight, minimumHeight)
: implicitHeight,
maximumHeight)
: parent.height
: statusBaseInput.implicitHeight, maximumHeight) : parent.height
color: Theme.palette.baseColor2
radius: 8
@@ -78,151 +98,168 @@ Item {
return sensor.containsMouse ? Theme.palette.primaryColor2 : "transparent"
}
StatusIcon {
id: statusIcon
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
anchors.topMargin: 10
icon: statusBaseInput.icon.name
width: statusBaseInput.icon.width
height: statusBaseInput.icon.height
color: Theme.palette.baseColor1
visible: !!statusBaseInput.icon.name
}
Flickable {
id: flick
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: statusIcon.visible ? statusIcon.right : parent.left
anchors.right: parent.right
anchors.leftMargin: statusIcon.visible ? 8 : statusBaseInput.leftPadding
anchors.rightMargin: statusBaseInput.rightPadding + clearable ? clearButton.width : 0
anchors.topMargin: statusBaseInput.topPadding
anchors.bottomMargin: statusBaseInput.bottomPadding
contentWidth: edit.paintedWidth
contentHeight: edit.paintedHeight
clip: true
QC.ScrollBar.vertical: QC.ScrollBar { interactive: multiline }
function ensureVisible(r) {
if (contentX >= r.x)
contentX = r.x;
else if (contentX+width <= r.x+r.width)
contentX = r.x+r.width-width;
if (contentY >= r.y)
contentY = r.y;
else if (contentY+height <= r.y+r.height)
contentY = r.y+r.height-height;
}
TextEdit {
id: edit
property string previousText: text
width: flick.width
selectByMouse: true
selectionColor: Theme.palette.primaryColor2
selectedTextColor: color
anchors.verticalCenter: parent.verticalCenter
focus: true
font.pixelSize: 15
font.family: Theme.palette.baseFont.name
color: Theme.palette.directColor1
onCursorRectangleChanged: flick.ensureVisible(cursorRectangle)
wrapMode: statusBaseInput.multiline ? Text.WrapAtWordBoundaryOrAnywhere : TextEdit.NoWrap
onActiveFocusChanged: {
if (statusBaseInput.pristine) {
statusBaseInput.pristine = false
}
}
Keys.onReturnPressed: {
if (multiline) {
event.accepted = false
} else {
event.accepted = true
}
}
Keys.onEnterPressed: {
if (multiline) {
event.accepted = false
} else {
event.accepted = true
}
}
onTextChanged: {
statusBaseInput.dirty = true
if (statusBaseInput.maximumLength > 0) {
if (text.length > statusBaseInput.maximumLength) {
var cursor = cursorPosition;
text = previousText;
if (cursor > text.length) {
cursorPosition = text.length;
} else {
cursorPosition = cursor-1;
}
}
previousText = text
}
}
StatusBaseText {
id: placeholder
visible: edit.text.length === 0
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 15
elide: StatusBaseText.ElideRight
font.family: Theme.palette.baseFont.name
color: statusBaseInput.enabled ? Theme.palette.baseColor1 :
Theme.palette.directColor6
}
}
} // Flickable
MouseArea {
id: sensor
enabled: !edit.activeFocus
hoverEnabled: true
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: edit.forceActiveFocus()
}
onClicked: {
edit.forceActiveFocus()
}
StatusIcon {
id: statusIcon
anchors.topMargin: 10
anchors.left: statusBaseInput.leftIcon ? parent.left : undefined
anchors.right: !statusBaseInput.leftIcon ? parent.right : undefined
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
icon: statusBaseInput.icon.name
width: statusBaseInput.icon.width
height: statusBaseInput.icon.height
color: statusBaseInput.icon.color
visible: !!statusBaseInput.icon.name
}
Flickable {
id: flick
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: (statusIcon.visible && statusBaseInput.leftIcon) ?
statusIcon.right : parent.left
anchors.right: {
if (!!statusBaseInput.component) {
return statusBaseInputComponentSlot.left
}
return statusIcon.visible && !statusBaseInput.leftIcon ? statusIcon.left : parent.right
}
anchors.leftMargin: statusIcon.visible && statusBaseInput.leftIcon ? 8
: statusBaseInput.leftPadding
anchors.rightMargin: {
return clearable ? clearButtonLoader.width + 12 :
(statusIcon.visible && !leftIcon) || !!statusBaseInput.component ? 8 : 0
}
anchors.topMargin: statusBaseInput.topPadding
anchors.bottomMargin: statusBaseInput.bottomPadding
contentWidth: edit.paintedWidth
contentHeight: edit.paintedHeight
boundsBehavior: Flickable.StopAtBounds
QC.ScrollBar.vertical: QC.ScrollBar { interactive: multiline; enabled: multiline }
function ensureVisible(r) {
if (contentX >= r.x)
contentX = r.x;
else if (contentX+width <= r.x+r.width)
contentX = r.x+r.width-width;
if (contentY >= r.y)
contentY = r.y;
else if (contentY+height <= r.y+r.height)
contentY = r.y+r.height-height;
}
TextEdit {
id: edit
property string previousText: text
width: flick.width
height: flick.height
verticalAlignment: Text.AlignVCenter
selectByMouse: true
selectionColor: Theme.palette.primaryColor2
selectedTextColor: color
focus: true
font.pixelSize: 15
font.family: Theme.palette.baseFont.name
color: Theme.palette.directColor1
onCursorRectangleChanged: { flick.ensureVisible(cursorRectangle); }
wrapMode: statusBaseInput.multiline ? Text.WrapAtWordBoundaryOrAnywhere : TextEdit.NoWrap
onActiveFocusChanged: {
if (statusBaseInput.pristine) {
statusBaseInput.pristine = false
}
}
Keys.onReturnPressed: {
if (multiline) {
event.accepted = false
} else {
event.accepted = true
}
}
Keys.onEnterPressed: {
if (multiline) {
event.accepted = false
} else {
event.accepted = true
}
}
Keys.forwardTo: [statusBaseInput]
onTextChanged: {
statusBaseInput.dirty = true
if (statusBaseInput.maximumLength > 0) {
if (text.length > statusBaseInput.maximumLength) {
var cursor = cursorPosition;
text = previousText;
if (cursor > text.length) {
cursorPosition = text.length;
} else {
cursorPosition = cursor-1;
}
}
previousText = text
}
}
StatusBaseText {
id: placeholder
visible: (edit.text.length === 0)
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: statusBaseInput.rightPadding
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 15
elide: StatusBaseText.ElideRight
font.family: Theme.palette.baseFont.name
color: statusBaseInput.enabled ? Theme.palette.baseColor1 :
Theme.palette.directColor6
}
}
} // Flickable
Item {
id: statusBaseInputComponentSlot
anchors.right: parent.right
anchors.rightMargin: 12
width: childrenRect.width
height: childrenRect.height
anchors.verticalCenter: parent.verticalCenter
}
}
} // Rectangle
StatusFlatRoundButton {
id: clearButton
visible: edit.text.length != 0 &&
statusBaseInput.clearable &&
!statusBaseInput.multiline &&
edit.activeFocus
anchors.right: parent.right
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
type: StatusFlatRoundButton.Type.Secondary
width: 24
height: 24
icon.name: "clear"
icon.width: 16
icon.height: 16
icon.color: Theme.palette.baseColor1
onClicked: {
edit.clear()
Loader {
id: clearButtonLoader
sourceComponent: StatusFlatRoundButton {
id: clearButton
visible: edit.text.length != 0 &&
statusBaseInput.clearable &&
!statusBaseInput.multiline &&
edit.activeFocus
type: StatusFlatRoundButton.Type.Secondary
width: 24
height: 24
icon.name: "clear"
icon.width: 16
icon.height: 16
icon.color: Theme.palette.baseColor1
onClicked: {
edit.clear()
}
}
}
}
+1
View File
@@ -36,6 +36,7 @@ CheckBox {
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
width: parent.width
color: Theme.palette.directColor1
leftPadding: !!statusCheckBox.text ? statusCheckBox.indicator.width + statusCheckBox.spacing
: statusCheckBox.indicator.width
}
+111 -26
View File
@@ -8,7 +8,7 @@ import StatusQ.Controls.Validators 0.1
Item {
id: root
implicitWidth: 480
height: (label.visible ?
height: (label.visible ?
label.anchors.topMargin +
label.height :
charLimitLabel.visible ?
@@ -16,57 +16,140 @@ Item {
charLimitLabel.height :
0) +
statusBaseInput.anchors.topMargin +
statusBaseInput.height +
(errorMessage.visible ?
statusBaseInput.height +
(errorMessage.visible ?
errorMessage.anchors.topMargin +
errorMessage.height :
0) + 8
property alias input: statusBaseInput
property alias valid: statusBaseInput.valid
property alias pending: statusBaseInput.pending
property alias text: statusBaseInput.text
property string label: ""
property string secondaryLabel: ""
property int charLimit: 0
property string errorMessage: ""
property real leftPadding: 16
property real rightPadding: 16
property list<StatusValidator> validators
property list<StatusValidator> asyncValidators
property int validationMode: StatusInput.ValidationMode.OnlyWhenDirty
property var pendingValidators: []
enum ValidationMode {
OnlyWhenDirty, // validates input only after it has become dirty
Always // validates input even before it has become dirty
}
property var errors: ({})
property var asyncErrors: ({})
function reset() {
statusBaseInput.valid = false
statusBaseInput.pristine = true
statusBaseInput.text = ""
errorMessage = ""
}
function validate() {
if (!statusBaseInput.dirty && validationMode === StatusInput.ValidationMode.OnlyWhenDirty) {
return
}
statusBaseInput.valid = true
if (validators.length) {
for (let idx in validators) {
let result = validators[idx].validate(statusBaseInput.text)
let validator = validators[idx]
let result = validator.validate(statusBaseInput.text)
if (typeof result === "boolean" && result) {
statusBaseInput.valid = true
statusBaseInput.valid = statusBaseInput.valid && true
delete errors[validator.name]
} else {
if (!errors) {
errors = {}
}
errors[validators[idx].name] = result
statusBaseInput.valid = false
result.errorMessage = validator.errorMessage
errors[validator.name] = result
statusBaseInput.valid = statusBaseInput.valid && false
}
}
if (errors){
let errs = Object.values(errors)
if (errs && errs[0]) {
errorMessage.text = errs[0].errorMessage || root.errorMessage;
} else {
errorMessage.text = ""
}
}
}
if (asyncValidators.length && !Object.values(errors).length) {
root.pending = true
for (let idx in asyncValidators) {
let asyncValidator = asyncValidators[idx]
if (pendingValidators.indexOf(asyncValidator.name) == -1) {
asyncValidator.input = root
pendingValidators.push(asyncValidator.name)
asyncValidator.validate(statusBaseInput.text)
}
}
}
}
function updateValidity(validatorName, result) {
if (!asyncErrors) {
asyncErrors = {}
}
if (typeof result === "boolean" && result) {
if (asyncErrors[validatorName] !== undefined) {
delete asyncErrors[validatorName]
}
errorMessage.text = ""
} else {
asyncErrors[validatorName] = result
for (let idx in asyncValidators) {
errorMessage.text = asyncValidators[idx].errorMessage || root.errorMessage
break;
}
}
pendingValidators = pendingValidators.filter(v => v !== validatorName)
root.pending = pendingValidators.length > 0
root.valid = Object.values(asyncErrors).length == 0
}
Component.onCompleted: validate()
StatusBaseText {
id: label
height: visible ? 17 : 0
Row {
id: labelRow
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: visible ? 8 : 0
anchors.leftMargin: 16
anchors.right: charLimitLabel.visible ? charLimitLabel.left : parent.right
anchors.rightMargin: 16
anchors.leftMargin: root.leftPadding
anchors.right: (charLimitLabel.visible ? charLimitLabel.right : parent.right)
anchors.rightMargin: root.rightPadding
height: visible ? 17 : 0
visible: !!root.label
elide: Text.ElideRight
spacing: 5
StatusBaseText {
id: label
elide: Text.ElideRight
text: root.label
font.pixelSize: 15
color: statusBaseInput.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1
}
text: root.label
font.pixelSize: 15
color: statusBaseInput.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1
StatusBaseText {
id: secondaryLabel
height: visible ? 17 : 0
visible: !!root.secondaryLabel
elide: Text.ElideRight
text: root.secondaryLabel
font.pixelSize: 15
color: Theme.palette.baseColor1
}
}
StatusBaseText {
@@ -75,7 +158,7 @@ Item {
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: visible ? 11 : 0
anchors.rightMargin: 16
anchors.rightMargin: root.rightPadding
visible: root.charLimit > 0
text: "%1 / %2".arg(statusBaseInput.text.length).arg(root.charLimit)
@@ -85,16 +168,17 @@ Item {
StatusBaseInput {
id: statusBaseInput
anchors.top: label.visible ? label.bottom :
anchors.top: labelRow.visible ? labelRow.bottom :
charLimitLabel.visible ? charLimitLabel.bottom : parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: charLimitLabel.visible ? 11 : 8
anchors.leftMargin: 16
anchors.rightMargin: 16
anchors.leftMargin: root.leftPadding
anchors.rightMargin: root.rightPadding
maximumLength: root.charLimit
onTextChanged: root.validate()
Keys.forwardTo: [root]
}
StatusBaseText {
@@ -103,16 +187,17 @@ Item {
anchors.top: statusBaseInput.bottom
anchors.topMargin: 11
anchors.right: parent.right
anchors.rightMargin: 16
anchors.rightMargin: root.rightPadding
anchors.left: parent.left
anchors.leftMargin: 16
anchors.leftMargin: root.leftPadding
height: visible ? implicitHeight : 0
visible: !!root.errorMessage && !statusBaseInput.valid
visible: !!text && !statusBaseInput.valid
font.pixelSize: 12
color: Theme.palette.dangerColor1
text: root.errorMessage
horizontalAlignment: Text.AlignRight
wrapMode: Text.WordWrap
}
@@ -0,0 +1,14 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import StatusQ.Core.Theme 0.1
TabBar {
id: statusSwitchTabBar
padding: 1
background: Rectangle {
implicitHeight: 36
color: Theme.palette.directColor7
radius: 8
}
}
@@ -0,0 +1,52 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtGraphicalEffects 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
TabButton {
id: statusSwitchTabButton
contentItem: Item {
height: 36
MouseArea {
id: sensor
hoverEnabled: true
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: mouse.accepted = false
onReleased: mouse.accepted = false
StatusBaseText {
id: label
text: statusSwitchTabButton.text
color: Theme.palette.primaryColor1
font.weight: Font.Medium
font.pixelSize: 15
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
}
}
}
background: Rectangle {
id: controlBackground
implicitHeight: 36
implicitWidth: 148
color: statusSwitchTabButton.checked ?
Theme.palette.statusSwitchTab.backgroundColor :
"transparent"
radius: 8
layer.enabled: true
layer.effect: DropShadow {
horizontalOffset: 0
verticalOffset: 0
radius: 10
samples: 25
spread: 0
color: Theme.palette.dropShadow
}
}
}
@@ -0,0 +1,15 @@
import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1
StatusValidator {
name: "addressOrEns"
errorMessage: "Please enter a valid address or ENS name."
validate: function (t) {
return Utils.isValidAddress(t) || Utils.isValidEns(t) ?
true :
{ actual: t }
}
}
@@ -0,0 +1,13 @@
import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1
StatusValidator {
name: "address"
errorMessage: "Please enter a valid address."
validate: function (t) {
return Utils.isValidAddress(t) ? true : { actual: t }
}
}
@@ -6,6 +6,12 @@ StatusValidator {
name: "minLength"
errorMessage: {
minLength === 1 ?
"Please enter a value" :
`The value must be at least ${minLength} characters.`
}
validate: function (value) {
return value.length >= minLength ? true : {
min: minLength,
@@ -1,9 +1,12 @@
import QtQuick 2.13
import StatusQ.Controls 0.1
QtObject {
id: statusValidator
property string name: ""
property string errorMessage: "invalid input"
property StatusInput input
property var validate: function (value) {
return true
+2
View File
@@ -1,5 +1,7 @@
module StatusQ.Controls.Validators
StatusAddressValidator 0.1 StatusAddressValidator.qml
StatusAddressOrEnsValidator 0.1 StatusAddressOrEnsValidator.qml
StatusValidator 0.1 StatusValidator.qml
StatusMinLengthValidator 0.1 StatusMinLengthValidator.qml
StatusMaxLengthValidator 0.1 StatusMaxLengthValidator.qml
+2
View File
@@ -17,3 +17,5 @@ StatusSlider 0.1 StatusSlider.qml
StatusBaseInput 0.1 StatusBaseInput.qml
StatusInput 0.1 StatusInput.qml
StatusPickerButton 0.1 StatusPickerButton.qml
StatusSwitchTabButton 0.1 StatusSwitchTabButton.qml
StatusSwitchTabBar 0.1 StatusSwitchTabBar.qml
@@ -5,6 +5,7 @@ QtObject {
property string subTitle
property int titleElide: Text.ElideRight
property int subTitleElide: Text.ElideRight
property Component popupMenu
property StatusImageSettings image: StatusImageSettings {
width: 40
height: 40
@@ -68,6 +68,30 @@ ThemePalette {
source: "../../../assets/fonts/InterStatus/InterStatus-Black.otf"
}
property QtObject codeFont: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-Regular.ttf"
}
property QtObject codeFontThin: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-Thin.ttf"
}
property QtObject codeFontExtraLight: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-ExtraLight.ttf"
}
property QtObject codeFontLight: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-Light.ttf"
}
property QtObject codeFontMedium: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-Medium.ttf"
}
property QtObject codeFontBold: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-Bold.ttf"
}
property color dropShadow: getColor('black', 0.08)
baseColor1: getColor('graphite5')
@@ -177,5 +201,9 @@ ThemePalette {
property QtObject statusChatInput: QtObject {
property color secondaryBackgroundColor: "#414141"
}
property QtObject statusSwitchTab: QtObject {
property color backgroundColor: baseColor3
}
}
@@ -68,6 +68,30 @@ ThemePalette {
source: "../../../assets/fonts/InterStatus/InterStatus-Black.otf"
}
property QtObject codeFont: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-Regular.ttf"
}
property QtObject codeFontThin: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-Thin.ttf"
}
property QtObject codeFontExtraLight: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-ExtraLight.ttf"
}
property QtObject codeFontLight: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-Light.ttf"
}
property QtObject codeFontMedium: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-Medium.ttf"
}
property QtObject codeFontBold: FontLoader {
source: "../../../assets/fonts/RobotoMono/RobotoMono-Bold.ttf"
}
baseColor1: getColor('grey5')
baseColor2: getColor('grey4')
baseColor3: getColor('grey3')
@@ -175,5 +199,9 @@ ThemePalette {
property QtObject statusChatInput: QtObject {
property color secondaryBackgroundColor: "#E2E6E8"
}
property QtObject statusSwitchTab: QtObject {
property color backgroundColor: white
}
}
+11
View File
@@ -24,6 +24,13 @@ QtObject {
property FontLoader monoFontExtraBold
property FontLoader monoFontBlack
property FontLoader codeFont
property FontLoader codeFontThin
property FontLoader codeFontExtraLight
property FontLoader codeFontLight
property FontLoader codeFontMedium
property FontLoader codeFontBold
property color black: getColor('black')
property color white: getColor('white')
@@ -139,6 +146,10 @@ QtObject {
property color secondaryBackgroundColor
}
property QtObject statusSwitchTab: QtObject {
property color backgroundColor
}
function alphaColor(color, alpha) {
let actualColor = Qt.darker(color, 1)
actualColor.a = alpha
+12
View File
@@ -14,6 +14,18 @@ QtObject {
}
return returnPos;
}
function isValidAddress(inputValue) {
return inputValue !== "0x" && /^0x[a-fA-F0-9]{40}$/.test(inputValue)
}
function isValidEns(inputValue) {
if (!inputValue) {
return false
}
const isEmail = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(inputValue)
const isDomain = /(?:(?:(?<thld>[\w\-]*)(?:\.))?(?<sld>[\w\-]*))\.(?<tld>[\w\-]*)/.test(inputValue)
return isEmail || isDomain || (inputValue.startsWith("@") && inputValue.length > 1)
}
}
@@ -29,6 +29,7 @@ SplitView {
SplitView.minimumWidth: 300
SplitView.fillWidth: true
SplitView.fillHeight: true
leftPadding: -5
contentItem: (!!centerPanel) ? centerPanel : null
background: Rectangle {
anchors.fill: parent
+7 -16
View File
@@ -11,12 +11,9 @@ import "statusModal" as Spares
QC.Popup {
id: statusModal
property Component content
property alias headerActionButton: headerImpl.actionButton
property StatusModalHeaderSettings header: StatusModalHeaderSettings {}
property alias contentComponent: contentLoader.item
property alias rightButtons: footerImpl.rightButtons
property alias leftButtons: footerImpl.leftButtons
property bool showHeader: true
@@ -27,10 +24,11 @@ QC.Popup {
parent: QC.Overlay.overlay
width: 480
height: contentItem.implicitHeight
margins: 0
padding: 0
topPadding: headerImpl.implicitHeight
bottomPadding: footerImpl.implicitHeight
leftPadding: 0
rightPadding: 0
modal: true
@@ -42,12 +40,10 @@ QC.Popup {
background: Rectangle {
color: Theme.palette.statusModal.backgroundColor
radius: 8
}
contentItem: Column {
width: parent.width
Spares.StatusModalHeader {
id: headerImpl
anchors.top: parent.top
width: visible ? parent.width : 0
visible: statusModal.showHeader
@@ -57,20 +53,15 @@ QC.Popup {
subTitleElide: header.subTitleElide
image: header.image
icon: header.icon
popupMenu: header.popupMenu
onEditButtonClicked: statusModal.editButtonClicked()
onClose: statusModal.close()
}
Loader {
id: contentLoader
width: parent.width
active: true
sourceComponent: statusModal.content
}
Spares.StatusModalFooter {
id: footerImpl
anchors.bottom: parent.bottom
width: visible ? parent.width : 0
visible: statusModal.showFooter
}
+4
View File
@@ -20,6 +20,10 @@ Menu {
property var openHandler
property var closeHandler
dim: true
Overlay.modeless: MouseArea {}
signal menuItemClicked(int menuIndex)
onOpened: {
+12 -3
View File
@@ -17,7 +17,7 @@ StatusModal {
showHeader: false
showFooter: false
property string searchText: contentComponent.searchText
property string searchText: contentItem.searchText
property string noResultsLabel: "No results"
property string defaultSearchLocationText: "Anywhere"
property bool loading
@@ -51,10 +51,19 @@ StatusModal {
setSearchSelection(defaultSearchLocationText, "", "", false, "", "transparent")
}
content: Item {
function forceActiveFocus() {
contentItem.searchInput.forceActiveFocus()
}
onOpened: {
forceActiveFocus();
}
contentItem: Item {
width: parent.width
height: root.height
property alias searchText: inputText.text
property alias searchInput: inputText
ColumnLayout {
id: contentItemColumn
@@ -348,6 +357,6 @@ StatusModal {
onClosed: {
root.resetSearchSelection();
root.loading = false;
contentComponent.searchText = "";
contentItem.searchText = "";
}
}
@@ -119,6 +119,7 @@ Row {
color:Theme.palette.baseColor1
width: parent.width
elide: statusImageWithTitle.subTitleElide
visible: !!statusImageWithTitle.subTitle
}
}
}
@@ -18,6 +18,7 @@ Rectangle {
property alias image: imageWithTitle.image
property alias icon: imageWithTitle.icon
property bool editable: false
property Component popupMenu
signal editButtonClicked
signal close
@@ -29,6 +30,12 @@ Rectangle {
color: Theme.palette.statusModal.backgroundColor
onPopupMenuChanged: {
if (!!popupMenu) {
popupMenuSlot.sourceComponent = popupMenu
}
}
StatusImageWithTitle {
id: imageWithTitle
anchors.verticalCenter: parent.verticalCenter
@@ -42,6 +49,15 @@ Rectangle {
onEditButtonClicked: statusModalHeader.editButtonClicked()
}
MouseArea {
anchors.fill: imageWithTitle
visible: !!statusModalHeader.popupMenu
cursorShape: Qt.PointingHandCursor
onClicked: {
popupMenuSlot.item.popup(imageWithTitle.x, imageWithTitle.y + imageWithTitle.height + 8)
}
}
Loader {
id: actionButtonLoader
anchors.right: closeButton.left
@@ -78,4 +94,9 @@ Rectangle {
width: parent.width
}
}
Loader {
id: popupMenuSlot
active: !!statusModalHeader.popupMenu
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -2
View File
@@ -1,4 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4083 12C15.4083 13.8823 13.8824 15.4082 12 15.4082C10.1177 15.4082 8.5918 13.8823 8.5918 12C8.5918 10.1177 10.1177 8.59174 12 8.59174C13.8824 8.59174 15.4083 10.1177 15.4083 12ZM13.9083 12C13.9083 13.0539 13.0539 13.9082 12 13.9082C10.9461 13.9082 10.0918 13.0539 10.0918 12C10.0918 10.9461 10.9461 10.0917 12 10.0917C13.0539 10.0917 13.9083 10.9461 13.9083 12Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2772 23H11.7228C10.8413 23 10.0666 22.4156 9.82441 21.568L9.3263 19.8247C9.22927 19.485 8.84755 19.3171 8.53163 19.4751L6.9006 20.2906C6.14049 20.6707 5.22247 20.5217 4.62156 19.9208L4.07924 19.3784C3.47832 18.7775 3.32935 17.8595 3.7094 17.0994L4.52491 15.4684C4.68287 15.1525 4.51495 14.7707 4.17534 14.6737L2.43196 14.1756C1.58437 13.9334 1 13.1587 1 12.2772V11.7228C1 10.8413 1.58437 10.0666 2.43196 9.82441L4.17534 9.3263C4.51495 9.22927 4.68287 8.84755 4.52491 8.53163L3.7094 6.9006C3.32935 6.14049 3.47832 5.22247 4.07924 4.62156L4.62156 4.07924C5.22247 3.47832 6.14049 3.32935 6.9006 3.7094L8.53163 4.52491C8.84755 4.68287 9.22927 4.51495 9.3263 4.17534L9.82441 2.43196C10.0666 1.58436 10.8413 1 11.7228 1H12.2772C13.1587 1 13.9334 1.58436 14.1756 2.43196L14.6737 4.17534C14.7707 4.51495 15.1525 4.68287 15.4684 4.52491L17.0994 3.7094C17.8595 3.32935 18.7775 3.47832 19.3784 4.07924L19.9208 4.62156C20.5217 5.22247 20.6707 6.14049 20.2906 6.9006L19.4751 8.53163C19.3171 8.84755 19.485 9.22927 19.8247 9.3263L21.568 9.82441C22.4156 10.0666 23 10.8413 23 11.7228V12.2772C23 13.1587 22.4156 13.9334 21.568 14.1756L19.8247 14.6737C19.485 14.7707 19.3171 15.1525 19.4751 15.4684L20.2906 17.0994C20.6707 17.8595 20.5217 18.7775 19.9208 19.3784L19.3784 19.9208C18.7775 20.5217 17.8595 20.6707 17.0994 20.2906L15.4684 19.4751C15.1525 19.3171 14.7707 19.485 14.6737 19.8247L14.1756 21.568C13.9334 22.4156 13.1587 23 12.2772 23ZM11.7228 21.5C11.511 21.5 11.3249 21.3596 11.2667 21.156L10.7686 19.4126C10.4135 18.1699 9.01678 17.5555 7.86081 18.1334L6.22978 18.949C6.04716 19.0403 5.82659 19.0045 5.68222 18.8601L5.1399 18.3178C4.99552 18.1734 4.95973 17.9528 5.05104 17.7702L5.86655 16.1392C6.44454 14.9832 5.8301 13.5865 4.58742 13.2314L2.84404 12.7333C2.6404 12.6751 2.5 12.489 2.5 12.2772V11.7228C2.5 11.511 2.6404 11.3249 2.84404 11.2667L4.58742 10.7686C5.83011 10.4135 6.44454 9.01678 5.86656 7.86081L5.05104 6.22978C4.95973 6.04716 4.99552 5.82659 5.1399 5.68222L5.68222 5.1399C5.82659 4.99552 6.04716 4.95973 6.22978 5.05104L7.86081 5.86655C9.01677 6.44454 10.4135 5.83011 10.7686 4.58742L11.2667 2.84404C11.3249 2.6404 11.511 2.5 11.7228 2.5H12.2772C12.489 2.5 12.6751 2.6404 12.7333 2.84404L13.2314 4.58742C13.5865 5.83011 14.9832 6.44454 16.1392 5.86655L17.7702 5.05104C17.9528 4.95973 18.1734 4.99552 18.3178 5.1399L18.8601 5.68222C19.0045 5.82659 19.0403 6.04716 18.949 6.22978L18.1334 7.86081C17.5555 9.01678 18.1699 10.4135 19.4126 10.7686L21.156 11.2667C21.3596 11.3249 21.5 11.511 21.5 11.7228V12.2772C21.5 12.489 21.3596 12.6751 21.156 12.7333L19.4126 13.2314C18.1699 13.5865 17.5555 14.9832 18.1334 16.1392L18.949 17.7702C19.0403 17.9528 19.0045 18.1734 18.8601 18.3178L18.3178 18.8601C18.1734 19.0045 17.9528 19.0403 17.7702 18.949L16.1392 18.1334C14.9832 17.5555 13.5865 18.1699 13.2314 19.4126L12.7333 21.156C12.6751 21.3596 12.489 21.5 12.2772 21.5H11.7228Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 13C13.933 13 15.5 11.433 15.5 9.5C15.5 7.567 13.933 6 12 6C10.067 6 8.5 7.567 8.5 9.5C8.5 11.433 10.067 13 12 13ZM12 11.5C13.1046 11.5 14 10.6046 14 9.5C14 8.39543 13.1046 7.5 12 7.5C10.8954 7.5 10 8.39543 10 9.5C10 10.6046 10.8954 11.5 12 11.5Z" fill="black"/>
<path d="M16.4465 16.2731C16.8119 16.5572 16.822 17.0954 16.4657 17.3909C16.1611 17.6435 15.723 17.6 15.4043 17.3655C14.4513 16.6643 13.2741 16.25 12.0002 16.25C10.7262 16.25 9.54901 16.6643 8.59597 17.3655C8.27728 17.6 7.83919 17.6435 7.53463 17.3909C7.17828 17.0954 7.18836 16.5572 7.55383 16.2731C8.78161 15.3185 10.3245 14.75 12.0002 14.75C13.6758 14.75 15.2187 15.3185 16.4465 16.2731Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22ZM12 20.5C16.6944 20.5 20.5 16.6944 20.5 12C20.5 7.30558 16.6944 3.5 12 3.5C7.30558 3.5 3.5 7.30558 3.5 12C3.5 16.6944 7.30558 20.5 12 20.5Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 6.26471C3 5.84237 3.33579 5.5 3.75 5.5H10.5C10.9142 5.5 11.25 5.84237 11.25 6.26471C11.25 6.68704 10.9142 7.02941 10.5 7.02941H3.75C3.33579 7.02941 3 6.68704 3 6.26471Z" fill="black"/>
<path d="M13 6.26471C13 5.84237 13.3358 5.5 13.75 5.5H20.25C20.6642 5.5 21 5.84237 21 6.26471C21 6.68704 20.6642 7.02941 20.25 7.02941H13.75C13.3358 7.02941 13 6.68704 13 6.26471Z" fill="black"/>
<path d="M16 10.0882C16 9.6659 16.3358 9.32353 16.75 9.32353H20.25C20.6642 9.32353 21 9.6659 21 10.0882C21 10.5106 20.6642 10.8529 20.25 10.8529H16.75C16.3358 10.8529 16 10.5106 16 10.0882Z" fill="black"/>
<path d="M8 13.9118C8 13.4894 7.66421 13.1471 7.25 13.1471H3.75C3.33579 13.1471 3 13.4894 3 13.9118C3 14.3341 3.33579 14.6765 3.75 14.6765H7.25C7.66421 14.6765 8 14.3341 8 13.9118Z" fill="black"/>
<path d="M3 10.0882C3 9.6659 3.33579 9.32353 3.75 9.32353H13.5C13.9142 9.32353 14.25 9.6659 14.25 10.0882C14.25 10.5106 13.9142 10.8529 13.5 10.8529H3.75C3.33579 10.8529 3 10.5106 3 10.0882Z" fill="black"/>
<path d="M21 13.9118C21 13.4894 20.6642 13.1471 20.25 13.1471H10.5C10.0858 13.1471 9.75 13.4894 9.75 13.9118C9.75 14.3341 10.0858 14.6765 10.5 14.6765H20.25C20.6642 14.6765 21 14.3341 21 13.9118Z" fill="black"/>
<path d="M3 17.7353C3 17.313 3.33579 16.9706 3.75 16.9706H10.5C10.9142 16.9706 11.25 17.313 11.25 17.7353C11.25 18.1576 10.9142 18.5 10.5 18.5H3.75C3.33579 18.5 3 18.1576 3 17.7353Z" fill="black"/>
<path d="M13 17.7353C13 17.313 13.3358 16.9706 13.75 16.9706H20.25C20.6642 16.9706 21 17.313 21 17.7353C21 18.1576 20.6642 18.5 20.25 18.5H13.75C13.3358 18.5 13 18.1576 13 17.7353Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.45117 12.354C7.45117 9.59258 9.68975 7.354 12.4512 7.354H68.5677C71.3291 7.354 73.5677 9.59258 73.5677 12.354V68.3888C73.5677 71.1502 71.3291 73.3888 68.5677 73.3888H12.4512C9.68975 73.3888 7.45117 71.1502 7.45117 68.3888V12.354Z" fill="#4360DF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5094 7.35449C22.2517 7.35396 7.45117 22.1359 7.45117 40.3714C7.45117 58.6068 22.2517 73.3888 40.5094 73.3888C58.7672 73.3888 73.5677 58.6062 73.5677 40.3714C73.5677 22.1365 58.7672 7.35449 40.5094 7.35449Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M46.0856 40.0664C42.1234 40.2945 39.6403 39.3727 35.6776 39.6012C34.6948 39.6564 33.7187 39.7986 32.761 40.0264C33.3459 32.7072 38.5323 26.3046 45.5849 25.8976C49.9129 25.6482 54.2387 28.317 54.4733 32.6495C54.7042 36.9078 51.4533 39.7567 46.0861 40.0659L46.0856 40.0664ZM35.4456 55.0375C31.2995 55.2714 27.156 52.773 26.931 48.7187C26.7097 44.7333 29.8247 42.0671 34.9663 41.7777C38.7616 41.564 41.1407 42.4271 44.936 42.2129C45.8771 42.1614 46.8119 42.0283 47.73 41.8151C47.1707 48.6647 42.2023 54.6572 35.4456 55.0375ZM40.5094 7.35454C22.2517 7.354 7.45117 22.136 7.45117 40.3714C7.45117 58.6068 22.2517 73.3888 40.5094 73.3888C58.7672 73.3888 73.5677 58.6063 73.5677 40.3714C73.5677 22.1365 58.7672 7.354 40.5094 7.354" fill="#4360DF"/>
<path d="M12.4512 14.354H68.5677V0.354004H12.4512V14.354ZM66.5677 12.354V68.3888H80.5677V12.354H66.5677ZM68.5677 66.3888H12.4512V80.3888H68.5677V66.3888ZM14.4512 68.3888V12.354H0.451172V68.3888H14.4512ZM12.4512 66.3888C13.5557 66.3888 14.4512 67.2842 14.4512 68.3888H0.451172C0.451172 75.0162 5.82376 80.3888 12.4512 80.3888V66.3888ZM66.5677 68.3888C66.5677 67.2842 67.4631 66.3888 68.5677 66.3888V80.3888C75.1951 80.3888 80.5677 75.0162 80.5677 68.3888H66.5677ZM68.5677 14.354C67.4631 14.354 66.5677 13.4586 66.5677 12.354H80.5677C80.5677 5.72658 75.1951 0.354004 68.5677 0.354004V14.354ZM12.4512 0.354004C5.82376 0.354004 0.451172 5.72658 0.451172 12.354H14.4512C14.4512 13.4586 13.5557 14.354 12.4512 14.354V0.354004Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+5
View File
@@ -243,6 +243,7 @@
<file>src/assets/img/icons/add-contact.svg</file>
<file>src/assets/img/icons/add-circle.svg</file>
<file>src/assets/img/icons/activity.svg</file>
<file>src/assets/img/icons/seed-phrase.svg</file>
<file>src/StatusQ/Components/StatusChatListCategoryItem.qml</file>
<file>src/StatusQ/Components/StatusChatListCategory.qml</file>
<file>src/StatusQ/Controls/StatusBaseInput.qml</file>
@@ -263,5 +264,9 @@
<file>src/assets/img/icons/windows_titlebar/maximize.svg</file>
<file>src/assets/img/icons/windows_titlebar/minimise.svg</file>
<file>src/assets/img/icons/windows_titlebar/status.svg</file>
<file>src/StatusQ/Controls/StatusSwitchTabButton.qml</file>
<file>src/StatusQ/Controls/StatusSwitchTabBar.qml</file>
<file>src/StatusQ/Components/StatusExpandableItem.qml</file>
<file>src/assets/img/icons/snt.svg</file>
</qresource>
</RCC>