Compare commits

...
Author SHA1 Message Date
Boris Melnik e541370c96 fix(StatusNavigationListItem): Fix click arguments
Closes: https://github.com/status-im/status-desktop/issues/5872
2022-05-25 01:09:22 +03:00
r4bbit.eth 6dc6ebb2fb fix(StatusSeedPhraseInput): ensure suggested item has correct text color (#670) 2022-05-13 14:24:24 -04:00
Igor Sirotin 2d031244ca fix(StatusSlider): Fixed background behavior (#667) 2022-05-13 14:24:07 -04:00
Mikhail Rogachev 9498be05ef fix(StatusProgressBar): Hide text in bar if it does not fit (#672) 2022-05-13 14:05:37 -04:00
Igor Sirotin 1b25ddd2b5 feat(Emoji): Functions improvements (#673)
- Introduced `format] argument for `parse` function
- New `iconId(text)` function
2022-05-13 14:01:48 -04:00
Alexandra Betouni 9741f98b70 fix(StatusSeedPhraseInput): anchored component to its parent bottom
Needed for: https://github.com/status-im/status-desktop/issues/5669
2022-05-13 16:29:35 +02:00
Alexandra Betouni 02923f7947 feat(StatusInput): exposed letterIconName property (#671)
Exposed letterIconName to be set in StatusLetterIdenticon

Needed for: https://github.com/status-im/status-desktop/issues/5636
2022-05-13 13:38:49 +03:00
Jonathan Rainville 6c5957a492 fix(StatusTagSelector): hide contact when it is selected (#668) 2022-05-12 16:27:35 -04:00
Jonathan Rainville eb96e5e08d fix(StatusSeedPhraseInput): autocomplete when word not alone in the list
The problem was that if the clicked word (or tabbed) was not alone in the suggestion list, the done signal was not called. Now it is always called when clicking on using tab or enter.
2022-05-12 10:00:06 -04:00
Stefan 457e5fe4af fix(StatusSlider): improve handle draging area
Issues
- The slider only handle mouse events in its own control space.
- In oder to use the entire area it needs to be centered.

Fixes

- Center the content vertically (handle and slider bar)
- Height and implicitHeight defaults to heigh of the handle
- Add note to create awareness about the mouse handling requirements
2022-05-11 19:30:04 +03:00
Stefan 1c0d4b0133 improvement(StatusImageCrop): add option to limit to fill
Currently only fit image is supported as minimum limit
This change sdds option to fill crop window with image as
minimum zoom which is required by status-go crop function
2022-05-11 18:06:26 +03:00
Alexandra Betouni ba79f62fda fix(StatusTagSelector): Updated names list filtering (#659)
Fixes: https://github.com/status-im/status-desktop/issues/5642
2022-05-11 12:01:14 +03:00
Jonathan Rainville a9c79b0359 fix(StatusBaseInput): adjust emoji size and position in base input (#660) 2022-05-10 15:17:22 -04:00
Alexandra Betouni a668afef35 fix(StatusTagSelector): Adjusted name tags list width and scrolling (#653)
Needed for https://github.com/status-im/status-desktop/issues/5643
2022-05-10 19:04:27 +03:00
Patryk Osmaczko deb8b38b1f chore(translations): add missing qsTrs
issue: status-im/status-desktop#4544
2022-05-10 17:52:34 +02:00
Khushboo Mehta b4ebc520e1 fix(StatusBaseButton): Button component should keep its original width/size when it enters loading state
fixes #656
2022-05-10 10:53:45 +02:00
mishkarogachev 5def2102c7 fix(StatusTagSelector):Create new chat input is single line 2022-05-10 10:52:49 +02:00
mishkarogachev 0f511ebc60 fix(StatusListItem): Make text always fit boundaries 2022-05-10 07:44:25 +02:00
Alexandra Betouni b09210cf1f fix(StatusTagSelector): Added up/down hard keys navigation
Needed for: https://github.com/status-im/status-desktop/issues/5640
2022-05-10 07:42:01 +02:00
Noelia 3bdfc3e528 fix(StatusBaseButton): After loading, button keeps loader width, so the button size is bigger
When button changed from loading to their normal shape it kept the width of the loader as it was visible in the row component.

It has been added a binding between loader `visible` property and root `loading` property that fixes the issue with the width.

Also, removed some `break` leftovers.

Fixes #5606 --> https://github.com/status-im/status-desktop/issues/5606
2022-05-10 07:41:45 +02:00
mishkarogachev baf5948c90 fix(StatusSwitchTabBar): Update and unify colors of tabBar 2022-05-10 07:41:15 +02:00
Jonathan Rainville 4b1516c990 fix(StatusWizardStepper): make to value configurable 2022-05-10 07:40:56 +02:00
Jonathan Rainville 1a5990f24b fix(StatusBaseInput): fix typo in verticalAlignement 2022-05-10 07:40:56 +02:00
Khushboo Mehta 82e1845056 fix(StatusListPicker): Add an option to allign the menu with respective the buttons and keep right aligned as the default one
Also fixes search placeholder to be centrally aligned

fixes #5633
2022-05-10 07:40:26 +02:00
Jonathan Rainville 75a76afcf1 fix(StatusSeedPhraseInput): fix pasting word with spaces in them
This makes it possible to paste a word with a space in it. It will insert it correctly
2022-05-09 13:02:30 -04:00
Jonathan Rainville 0922cf507a fix(StatusSeedPhraseInput): fix interactions in SeedInput 2022-05-09 13:02:30 -04:00
Jonathan Rainville 32c5f7f8df feat(StatusBasenput): add acceptReturn to Input to pass return events 2022-05-09 13:02:30 -04:00
Jonathan Rainville 148505ba6b fix(StatusBaseInput): fix dirty being set when the text didn't change 2022-05-06 20:07:36 +02:00
Sale Djenic d960c6dcec feat(StatusToastMessage): introduce duration property and close signal emit after animation
- `open` property set to `true` by default
- `duration` property added (if set to anything greater than 0, will trigger
close toast signal after `duration` milliseconds)
- `close` signal is now emitted once animation gets completed
2022-05-06 07:51:19 +02:00
Stefan bb9f08155f fix(StatusImageCrop): output an error if trying to set cropRect without image 2022-05-05 17:16:13 +02:00
Stefan 934db52126 test(Components.StatusImageCropPanel): basic validation tests for the image crop component
updates #5401
2022-05-05 17:16:13 +02:00
Stefan dcef08f3b3 test(Controls.StatusImageCrop): basic validation tests for the image crop control
updates #5401
2022-05-05 17:16:13 +02:00
Stefan 20e7b345e1 tests(general): refactor tests
Enhance the CMake strucuture of thests
Update documentation
2022-05-05 17:16:13 +02:00
Stefan 5f8453659d feat(Sandbox): add visual test setup page for StatusImageCropPanel
Other improvements:

- save state for theme switcher and last selected page
- add optional fill all test space for pages

fixes: 5401
2022-05-05 17:16:13 +02:00
Stefan c7ff011649 feat(StatusImageCropPanel): Add image crop editor panel component
New QML component StatusQ.Components.StatusImageCropPanel
that extends on StatusImageCrop with simper interface and extra features

Features:
- Minimizes drawing with Canvas to the crop window
- Adds user interactions: pan, zoom
- Zoom slider for zooming, beside the mouse scroll action
- Optional checker pattern for background for the user to have a visual feedback on the transparent areas or image margins

fixes: #5401
updates: #5118
2022-05-05 17:16:13 +02:00
Stefan 2b71ccf50f feat(StatusImageCrop): Image cropping editor control
New QML control StatusQ.Controls.StatusImageCrop allows user to crop an image for avatar/banner purpuses

Features:
- Support rectangular and elliptical crop windows
- Transparency for the wall
- Controlling the crop window in image coordinates using cropRect
- The image support is based on `Image`
- Limits the

fixes: #5401
2022-05-05 17:16:13 +02:00
Alexandra Betouni 310be728a3 fix(StatusChatList): Added property to control item's highlight (#646)
Needed for https://github.com/status-im/status-desktop/issues/5627
2022-05-05 12:27:05 +03:00
Igor Sirotin e2728285e1 build: Added all qmls and assets to resources (fixes release build) (#647) 2022-05-04 21:19:46 +03:00
Igor Sirotin 1d9abf8e95 fix(StatusBaseInput): Fixed border color on active focus loss (#643) 2022-05-03 19:08:40 +03:00
Igor Sirotin 31e90c2d47 fix(StatusBaseInput): Implement cursor as per designs (#642) 2022-05-03 19:08:02 +03:00
Noelia ae1e72fb23 fix(StatusListPicker): Disable uncheck option and reset searcher (#639)
Disabled uncheck option if component is configured as single selection mode.

Added binding to reset searcher input text when picker is closed.

BREAKING CHANGE: Renamed `searchText` property for `placeholderSearchText` to control placeholder text in searcher and added / used existing `searchText` property to control  searcher input text.
2022-05-03 10:45:36 +02:00
Stefan Dunca 7710470559 doc(contributing.md): add pixel miss-alignment recommendation
Follow up on the PR comment https://github.com/status-im/status-desktop/pull/5549#issuecomment-1099468321
2022-04-16 20:28:27 +02:00
Michal Iskierko 2069399979 fix(StatusTagSelector): Fix displaying a list of contacts in StatusTagSelector
Fix #5465
2022-04-13 12:30:02 +02:00
Alexandra Betouni 29dd4da2e4 fix(StatusSearchLocationMenu): removed dimming from submenus (#635)
fixes https://github.com/status-im/status-desktop/issues/5139
2022-04-11 17:33:53 -04:00
Patryk Osmaczko 394040a039 chore(StatusChatList): use colorId if color is not provided 2022-04-08 13:19:00 +02:00
Patryk Osmaczko a8e1ed863c chore(StatusLetterIdenticon): test different backgrounds 2022-04-08 13:19:00 +02:00
Patryk Osmaczko d7b1ae9fbc fix(StatusIdenticonRing): repait ring when model changes 2022-04-08 13:19:00 +02:00
Patryk Osmaczko f0c7d950ea chore(ThemePalette): introduced userCustomizationColors
BREAKING CHANGE: renamed accountColors to userCustomizationColors
2022-04-08 13:19:00 +02:00
Patryk Osmaczko 1e5d389b53 feat(StatusLetterIdenticon): use luminance to determine letter color 2022-04-08 13:19:00 +02:00
Alexandra Betouni 1cda85fc0e feat(StatusInput): Adding component documentation (#632)
Part of #616
2022-04-07 16:51:21 -04:00
Alexandra Betouni 4999368dfe feat(StatusBaseInput): Adding component documentation (#631)
Part of #616
2022-04-07 16:50:36 -04:00
Noelia 7e4d69be05 feat(StatusPasswordStrengthIndicator): Added component documentation (#630)
Added component documentation.

Part of #620
2022-04-07 16:50:23 -04:00
Noelia 3abbd3d4cd feat(StatusMemberListItem): Added component documentation (#629)
Added component documentation and updated functions to be private.

Part of #620
2022-04-07 16:50:09 -04:00
Noelia 8fc5276090 feat(StatusBanner): Added component documentation (#628)
Added component documentation.

Part of #620
2022-04-07 16:49:44 -04:00
Alexandra Betouni cf4b092112 feat(StatusTagSelector): Adding component documentation (#623)
Part of #616
2022-04-07 16:49:14 -04:00
Alexandra Betouni ddf4a8d4f1 feat(StatusToastMessage): Adding component documentation (#621)
Part of #616
2022-04-07 16:49:02 -04:00
Alexandra Betouni 9c1edc80db feat(StatusSeedPhraseInput): Adding component documentation (#617)
Also fixed component's height in demonstration page

Part of #616
2022-04-07 16:48:43 -04:00
Alexandra Betouni 20bf5b07a6 fix(StatusTagSelector): adding wrap mode in text edit (#633)
needed for: https://github.com/status-im/status-desktop/issues/5294
2022-04-07 16:12:12 -04:00
Noelia ade6bd2d0d fix(StatusPickerButton): Added property textPixelSize to be configurable (#626)
Added property `textPixelSize` to be configurable in `StatusPickerButton`.
2022-04-07 11:13:37 -04:00
Noelia 125760b934 fix(StatusItemPicker): Updated selected property properly and documentation improvements. (#625)
Updated `selected` property properly.

Some documentation improvements.
2022-04-07 11:13:13 -04:00
Noelia e231c0a6cb fix(StatusListPicker/StatusItemPicker/StatusPickerButton): Added some properties / signals (#624)
Added property `textPixelSize` to be configurable in `StatusPickerButton`.

Added signal `itemPickerChanged` to be directly notified when an item changes its selected property value.

Updated `selected` property properly in `StatusListPicker`.

Added new property `enableSelectableItem` to highlight an item when hovering.

Some documentation improvements.
2022-04-07 11:12:57 -04:00
Pascal Precht a16bc6bb6e fix(StatusSeedPhraseInput): don't access input control via root 2022-04-07 14:32:07 +02:00
Richard Ramos 51989c0f5d fix(StatusSeedPhraseInput): handle 3 characters mnemonics 2022-04-07 13:15:50 +02:00
Boris Melnik 88b7f6eab8 fix(StatusListItem): Show emoji if exist
Part of: https://github.com/status-im/status-desktop/issues/5350
2022-04-06 13:58:05 +02:00
Alexandra Betouni 2df3c0a03e feat(StatusWizardStepper): Adding component documentation (#618)
Part of #616
2022-04-06 10:22:15 +02:00
Anthony Laibe 1e9cc07e2d fix(StatusFloatValidator): Fix value equal to bottom 2022-04-05 11:54:23 +02:00
Patryk Osmaczko 6221c0a540 fix: add various fixes/improvements to identicons 2022-04-04 17:39:52 +02:00
Boris Melnik ddbffa7f28 feat(assets): Add new icons for community settings
Part of: status-im/status-desktop#4932
2022-04-04 15:25:31 +02:00
Noelia 555ad8bea8 feat(StatusQ.Components/Controls): New StatusListPicker and StatusItemPicker selector component / control (#583)
It adds the `StatusItemPicker` control and `StatusListPicker` component and its corresponding documentation.

The new selector component is composed by a `StatusButtonPicker` and a drop-down list.

It adds sections in `ListView` by using `category` model property.

It incorporates a searcher in header.

The selector type can be customised as a `StatusRadioButton` or a `StatusCheckbox`.

It adds dynamic selection mechanism.

It adds a dynamic text fit between `name` and `shortName` text components.

It adds a specific page in `sandbox` and some models to play with that.

It extends `StatusPickerButton` to allow more than one type.

Closes #563
2022-04-04 12:16:47 +02:00
Stefan Dunca a561a3fff8 Clarify dynamic scoping 2022-04-04 10:49:24 +03:00
Stefan 70ec009edd docs(General): initial proposal on developer documentation and checklists
Developer targeted checklist and reminders to cover the most common source of errors
and tech-debt (maintenance burden).
2022-04-04 10:49:24 +03:00
Alexandra Betouni 31a1c60eb1 fix(StatusTagSelector): fixing tag selector height (#606)
Fix for https://github.com/status-im/status-desktop/issues/5199
2022-04-01 17:26:35 +03:00
Alexandra Betouni 0a2cf6ab6e fix(StatusSeedPhraseInput): added trim() also when seedWord selected from list (#613)
Fixes issue on Windows where empty spaces are insterted in the input
field together with the word
2022-03-31 19:40:21 +03:00
Jonathan Rainville 07fa2a1b0c fix(StatusSeedPhraseInput): remove empty spaces in seed phrase input (#611) 2022-03-30 14:23:18 -04:00
Sale Djenic 43b8e43303 fix(@desktop/contacts): placeholder position updated 2022-03-30 18:30:21 +02:00
Alexandra BetouniandKhushboo-dev-cpp 2cec51ff8d feat(StatusSeedPhraseInput): exposed keys on pressed signal (#599)
* feat(StatusSeedPhraseInput): exposed touch/keys pressed events

Exposed keys.onPressed signal, mouse on clicked signal
as well as tabNavItem to be set in KeyNavigation.tab
when needed

Needed for task https://github.com/status-im/status-desktop/issues/4955

* feat(StatusListItem): Added new tags inline in the StatusListItem (#607)

Co-authored-by: Khushboo-dev-cpp <60327365+Khushboo-dev-cpp@users.noreply.github.com>
2022-03-30 01:23:54 +03:00
Noelia 684f45cc08 fix(StatusChatListCategory): Selecting the category menu should not toggle the category itself
Closes `status-desktop` issue https://github.com/status-im/status-desktop/issues/4914
2022-03-29 11:50:29 +02:00
Stefan eae4e0f405 tests(StatusInput): add regression test to check for qml output
Also improves on the test structure
2022-03-28 22:52:36 +03:00
Michał Iskierko b2ac2794bb fix(StatusSearchPopup): Fix cutting text with big font (#604)
Decreasing padding.
Setting loading property default to false.

Fix #5197
2022-03-28 13:14:15 -04:00
Patryk Osmaczko b565a32c7e fix(StatusInput): add implicitHeight
It makes it work with layouts correctly.
2022-03-28 15:27:13 +02:00
Patryk Osmaczko a1835164d9 fix(StatusBaseInput): fix default padding when leftComponent is loaded 2022-03-28 15:26:39 +02:00
Alexandra Betouni 06e931d749 fix(StatusSeedPhraseInput): Increased suggestion match chars to 4 2022-03-26 07:46:11 +01:00
109 changed files with 14522 additions and 724 deletions
+1
View File
@@ -16,3 +16,4 @@ sandbox/qmlcache_loader.cpp
doc/html
CMakeLists.txt.user
.vscode
build/
+5
View File
@@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.19)
project(StatusQ)
add_subdirectory(tests)
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

+2
View File
@@ -22,6 +22,7 @@
\li \l{StatusDescriptionListItem}
\li \l{StatusLetterIdenticon}
\li \l{StatusListItem}
\li \l{StatusListPicker}
\li \l{StatusListSectionHeadline}
\li \l{StatusLoadingIndicator}
\li \l{StatusMemberListItem}
@@ -37,5 +38,6 @@
\li \l{StatusMessageDetails}
\li \l{StatusTagSelector}
\li \l{StatusToastMessage}
\li \l{StatusWizardStepper}
\endlist
*/
+2
View File
@@ -32,6 +32,7 @@
\li \l{StatusBaseInput}
\li \l{StatusIdenticonRing}
\li \l{StatusInput}
\li \l{StatusItemPicker}
\li \l{StatusPickerButton}
\li \l{StatusPinInput}
\li \l{StatusProgressBar}
@@ -43,5 +44,6 @@
\li \l{StatusWalletColorSelect}
\li \l{StatusRegularExpressionValidator}
\li \l{StatusIntValidator}
\li \l{StatusSeedPhraseInput}
\endlist
*/
+3
View File
@@ -7,6 +7,8 @@ Item {
id: themeSwitchWrapper
signal checkedChanged()
property alias lightThemeEnabled: switchControl.checked
width: themeSwitch.width
height: themeSwitch.height
@@ -26,6 +28,7 @@ Item {
}
StatusSwitch {
id: switchControl
onCheckedChanged: themeSwitchWrapper.checkedChanged()
StatusToolTip {
+27 -2
View File
@@ -4,6 +4,7 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Utils 0.1
GridLayout {
columns: 1
@@ -25,7 +26,7 @@ GridLayout {
badge.value: 1
}
StatusNavigationListItem {
title: "Menu Item (selected)"
title: "Menu Item (selected) with very long text"
selected: true
icon.name: "info"
badge.value: 1
@@ -338,6 +339,24 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
}
}
StatusListItem {
title: "List Item with Inline Tags"
icon.isLetterIdenticon: true
tagsModel : 2
tagsDelegate: StatusListItemTag {
color: "blue"
height: 24
radius: 6
closeButtonVisible: false
icon.emoji: "😁"
icon.emojiSize: Emoji.size.verySmall
icon.isLetterIdenticon: true
title: "helloworld.eth"
titleText.font.pixelSize: 12
titleText.color: Theme.palette.indirectColor1
}
}
StatusListItem {
title: "List Item with Emoji"
subTitle: "Emoji"
@@ -401,10 +420,16 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
trustIndicator: StatusContactVerificationIcons.TrustedType.Untrustworthy
}
StatusMemberListItem {
nickName: "very-long-annoying-nickname.eth"
isOnline: false
trustIndicator: StatusContactVerificationIcons.TrustedType.Untrustworthy
}
StatusMemberListItem {
nickName: "This girl I know from work"
userName: "annabelle"
isOnline: true
isOnline: true
}
StatusMemberListItem {
+49 -29
View File
@@ -4,38 +4,58 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
GridLayout {
columns: 6
columnSpacing: 5
rowSpacing: 5
ColumnLayout {
GridLayout {
Layout.fillWidth: true
Layout.fillHeight: true
StatusLoadingIndicator {
color: Theme.palette.directColor4
columns: 6
columnSpacing: 5
rowSpacing: 5
StatusLoadingIndicator {
color: Theme.palette.directColor4
}
StatusLetterIdenticon {
name: "#status"
}
StatusRoundedImage {
image.source: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
}
StatusBadge {}
StatusBadge {
value: 1
}
StatusBadge {
value: 10
}
StatusBadge {
value: 100
}
StatusRoundIcon {
icon.name: "info"
}
}
StatusLetterIdenticon {
name: "#status"
}
Flow {
Layout.fillWidth: true
spacing: 4
StatusRoundedImage {
image.source: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
}
StatusBadge {}
StatusBadge {
value: 1
}
StatusBadge {
value: 10
}
StatusBadge {
value: 100
}
StatusRoundIcon {
icon.name: "info"
Repeater {
model: 12
StatusLetterIdenticon {
name: "A"
color: Theme.palette.userCustomizationColors[index]
letterSize: 16
}
}
}
}
+4 -1
View File
@@ -33,7 +33,10 @@ Page {
onTextChanged: {
sortModel(root.contactsModel);
}
Component.onCompleted: { sortModel(root.contactsModel); }
Component.onCompleted: {
textEdit.forceActiveFocus();
sortModel(root.contactsModel);
}
}
StatusButton {
+12 -12
View File
@@ -41,7 +41,7 @@ StatusAppThreePanelLayout {
Layout.leftMargin: 17
implicitHeight: 36
topPadding: 8
bottomPadding: 0
bottomPadding: 8
placeholderText: "Search"
icon.name: "search"
}
@@ -74,6 +74,7 @@ StatusAppThreePanelLayout {
StatusChatList {
anchors.horizontalCenter: parent.horizontalCenter
model: Models.demoChatListItems
highlightItem: !root.createChat
onChatItemUnmuted: {
for (var i = 0; i < Models.demoChatListItems.count; i++) {
let item = Models.demoChatListItems.get(i);
@@ -128,19 +129,18 @@ StatusAppThreePanelLayout {
centerPanel: Loader {
anchors.fill: parent
sourceComponent: root.createChat ? createChatView : chatChannelView
}
Component {
id: createChatView
CreateChatView {
contactsModel: Models.dummyContactsModel
Component {
id: createChatView
CreateChatView {
contactsModel: Models.dummyContactsModel
}
}
}
Component {
id: chatChannelView
ChatChannelView {
model: Models.chatMessagesModel
Component {
id: chatChannelView
ChatChannelView {
model: Models.chatMessagesModel
}
}
}
+317 -11
View File
@@ -62,7 +62,9 @@ QtObject {
itemId: "x012340000"
name: "#status"
icon: ""
emoji: ""
isIdenticon: false
colorHash: []
color: "blue"
description: ""
type: StatusChatListItem.Type.PublicChat
@@ -78,7 +80,9 @@ QtObject {
itemId: "x012340001"
name: "status-desktop"
icon: ""
emoji: ""
isIdenticon: false
colorHash: []
color: "red"
description: ""
type: StatusChatListItem.Type.PublicChat
@@ -95,7 +99,9 @@ QtObject {
name: "Amazing Funny Squirrel"
icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAlklEQVR4nOzW0QmDQBAG4SSkl7SUQlJGCrElq9F3QdjjVhh/5nv3cFhY9vUIYQiNITSG0Bh
CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC"
emoji: ""
isIdenticon: true
colorHash: []
color: "green"
description: ""
type: StatusChatListItem.Type.OneToOneChat
@@ -111,7 +117,9 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
itemId: "x012340003"
name: "Black Ops"
icon: ""
emoji: ""
isIdenticon: false
colorHash: []
color: "purple"
description: ""
type: StatusChatListItem.Type.OneToOneChat
@@ -127,7 +135,9 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
itemId: "x012340004"
name: "Spectacular Growing Otter"
icon: ""
emoji: ""
isIdenticon: false
colorHash: []
color: "orange"
description: ""
type: StatusChatListItem.Type.OneToOneChat
@@ -143,7 +153,9 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
itemId: "x012340005"
name: "channel-with-a-super-duper-long-name"
icon: ""
emoji: ""
isIdenticon: false
colorHash: []
color: "green"
description: ""
type: StatusChatListItem.Type.PublicChat
@@ -397,7 +409,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
imageSource: ""
iconName: "chat"
iconColor: ""
isIdenticon: false
subItems: [
ListElement {
value: "sub_item_1_1"
@@ -405,7 +416,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
imageSource: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
iconName: ""
iconColor: ""
isIdenticon: false
},
ListElement {
value: "sub_item_1_2"
@@ -413,7 +423,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
imageSource: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAlklEQVR4nOzW0QmDQBAG4SSkl7SUQlJGCrElq9F3QdjjVhh/5nv3cFhY9vUIYQiNITSG0BhCExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC"
iconName: ""
iconColor: ""
isIdenticon: true
}]}
ListElement {
value: "item_2"
@@ -421,7 +430,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
imageSource: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
iconName: ""
iconColor: ""
isIdenticon: false
subItems: [
ListElement {
value: "sub_item_2_1"
@@ -429,7 +437,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
imageSource: ""
iconName: "channel"
iconColor: ""
isIdenticon: false
},
ListElement {
value: "sub_item_2_2"
@@ -437,7 +444,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
imageSource: ""
iconName: "channel"
iconColor: ""
isIdenticon: false
},
ListElement {
value: "sub_item_2_3"
@@ -445,7 +451,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
imageSource: ""
iconName: "channel"
iconColor: ""
isIdenticon: false
}]}
ListElement {
value: "item_3"
@@ -453,7 +458,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
imageSource: "";
iconName: "info"
iconColor: ""
isIdenticon: false
subItems: [
ListElement {
value: "sub_item_3_1"
@@ -461,7 +465,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
imageSource: ""
iconName: "channel"
iconColor: ""
isIdenticon: false
}]}
ListElement {
value: "item_4"
@@ -469,7 +472,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
imageSource: "";
iconName: ""
iconColor: "red"
isIdenticon: false
subItems: [
ListElement {
value: "sub_item_4_1"
@@ -477,7 +479,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
imageSource: ""
iconName: "channel"
iconColor: ""
isIdenticon: false
}]}
}
@@ -1052,4 +1053,309 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
notificationsCount: 0
}
}
property ListModel currencyPickerModel: ListModel {
ListElement {
key: 0
name: "United States Dollar"
shortName: "USD"
symbol: "$"
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
category: ""
selected: false
}
ListElement {
key: 1
name: "British Pound"
shortName: "GBP"
symbol: "£"
imageSource: "../../assets/twemoji/26x26/1f4b5.png"
category: ""
selected: false
}
ListElement {
key: 2
name: "Euro"
shortName: "EUR"
symbol: "€"
imageSource: "../../assets/twemoji/26x26/1f4b6.png"
category: ""
selected: true
}
ListElement {
key: 3
name: "Shout Korean Won"
shortName: "KRW"
symbol: "₩"
imageSource: "../../assets/twemoji/26x26/1f4b8.png"
category: ""
selected: false
}
ListElement {
key: 4
name: "Ethereum"
shortName: "ETH"
symbol: "Ξ"
imageSource: "../../assets/twemoji/26x26/1f4b7.png"
category: "Tokens"
selected: true
}
ListElement {
key: 5
name: "Bitcoin"
shortName: "BTC"
symbol: "฿"
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
category: "Tokens"
selected: false
}
ListElement {
key: 6
name: "Status Network Token"
shortName: "SNT"
symbol: ""
imageSource: "../../assets/twemoji/26x26/1f4b8.png"
category: "Tokens"
selected: false
}
ListElement {
key: 7
name: "Emirati Dirham"
shortName: "AED"
symbol: "د.إ"
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
category: "Other Fiat"
selected: false
}
ListElement {
key: 8
name: "Afghani"
shortName: "AFN"
symbol: "؋"
imageSource: "../../assets/twemoji/26x26/1f4b7.png"
category: "Other Fiat"
selected: false
}
ListElement {
key: 9
name: "Argentine Peso"
shortName: "AFN"
symbol: "$"
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
category: "Other Fiat"
selected: false
}
}
property ListModel currencyPickerModel2: ListModel {
ListElement {
key: 0
name: "United States Dollar"
shortName: "USD"
symbol: "$"
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
category: ""
selected: false
}
ListElement {
key: 1
name: "British Pound"
shortName: "GBP"
symbol: "£"
imageSource: "../../assets/twemoji/26x26/1f4b5.png"
category: ""
selected: false
}
ListElement {
key: 2
name: "Euro"
shortName: "EUR"
symbol: "€"
imageSource: "../../assets/twemoji/26x26/1f4b6.png"
category: ""
selected: true
}
ListElement {
key: 3
name: "Shout Korean Won"
shortName: "KRW"
symbol: "₩"
imageSource: "../../assets/twemoji/26x26/1f4b8.png"
category: ""
selected: false
}
ListElement {
key: 4
name: "Ethereum"
shortName: "ETH"
symbol: "Ξ"
imageSource: "../../assets/twemoji/26x26/1f4b7.png"
category: "Tokens"
selected: true
}
ListElement {
key: 5
name: "Bitcoin"
shortName: "BTC"
symbol: "฿"
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
category: "Tokens"
selected: false
}
ListElement {
key: 6
name: "Status Network Token"
shortName: "SNT"
symbol: ""
imageSource: "../../assets/twemoji/26x26/1f4b8.png"
category: "Tokens"
selected: false
}
ListElement {
key: 7
name: "Emirati Dirham"
shortName: "AED"
symbol: "د.إ"
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
category: "Other Fiat"
selected: false
}
ListElement {
key: 8
name: "Afghani"
shortName: "AFN"
symbol: "؋"
imageSource: "../../assets/twemoji/26x26/1f4b7.png"
category: "Other Fiat"
selected: false
}
ListElement {
key: 9
name: "Argentine Peso"
shortName: "AFN"
symbol: "$"
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
category: "Other Fiat"
selected: false
}
}
property ListModel languagePickerModel: ListModel {
ListElement {
key: 0
name: "English"
shortName: "English"
imageSource: "../../assets/twemoji/26x26/1f1ec-1f1e7.png"
category: ""
selected: false
}
ListElement {
key: 1
name: "Korean"
shortName: "한국어"
imageSource: "../../assets/twemoji/26x26/1f1f0-1f1f7.png"
category: ""
selected: false
}
ListElement {
key: 2
name: "Portuguese (Brazilian)"
shortName: "Português"
imageSource: "../../assets/twemoji/26x26/1f1e7-1f1f7.png"
category: ""
selected: true
}
ListElement {
key: 3
name: "Dutch"
shortName: "Nederlands"
imageSource: "../../assets/twemoji/26x26/1f1f3-1f1f1.png"
category: "Beta Languages"
selected: false
}
ListElement {
key: 4
name: "Indonesian"
shortName: "Bahasa Indonesia"
imageSource: "../../assets/twemoji/26x26/1f1ee-1f1e9.png"
category: "Beta Languages"
selected: false
}
ListElement {
key: 5
name: "Spanish"
shortName: "Español"
imageSource: "../../assets/twemoji/26x26/1f1ea-1f1e6.png"
category: "Beta Languages"
selected: false
}
}
property ListModel languageNoImagePickerModel: ListModel {
ListElement {
key: 0
name: "Chinese (Mainland China)"
shortName: "普通话"
category: ""
selected: true
}
ListElement {
key: 1
name: "Russian"
shortName: "Русский Язык"
category: ""
selected: false
}
ListElement {
key: 2
name: "Arabic"
shortName: "اَلْعَرَبِيَّةُ"
category: "Beta Languages"
selected: true
}
ListElement {
key: 3
name: "Chinese (Taiwan)"
shortName: "臺灣華語"
category: "Beta Languages"
selected: false
}
ListElement {
key: 4
name: "Filipino"
shortName: "Wikang Filipino"
category: "Beta Languages"
selected: false
}
ListElement {
key: 5
name: "French"
shortName: "Français"
category: "Beta Languages"
selected: false
}
ListElement {
key: 6
name: "Italian"
shortName: "Italiano"
category: "Beta Languages"
selected: false
}
ListElement {
key: 7
name: "Turkish"
shortName: "Türkçe"
category: "Beta Languages"
selected: false
}
ListElement {
key: 8
name: "Urdu"
shortName: "اُردُو"
category: "Beta Languages"
selected: false
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

+5
View File
@@ -11,6 +11,11 @@ int main(int argc, char *argv[])
#endif
SandboxApp app(argc, argv);
app.setOrganizationName("Status");
app.setOrganizationDomain("status.im");
app.setApplicationName("Sandbox");
app.startEngine();
return app.exec();
+43 -13
View File
@@ -2,6 +2,8 @@ import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.14
import QtGraphicalEffects 1.13
import QtQuick.Layouts 1.14
import Qt.labs.settings 1.0
import Sandbox 0.1
@@ -31,10 +33,7 @@ StatusWindow {
property real factor: 1.0
Component.onCompleted: {
Theme.palette = lightTheme
rootWindow.updatePosition();
}
Component.onCompleted: rootWindow.updatePosition()
QtObject {
id: appSectionType
@@ -110,12 +109,10 @@ StatusWindow {
anchors.topMargin: 32
anchors.right: parent.right
anchors.rightMargin: 32
onCheckedChanged: {
if (Theme.palette === rootWindow.lightTheme) {
Theme.palette = rootWindow.darkTheme
} else {
Theme.palette = rootWindow.lightTheme
}
lightThemeEnabled: storeSettings.lightTheme
onLightThemeEnabledChanged: {
Theme.palette = lightThemeEnabled ? rootWindow.darkTheme : rootWindow.lightTheme
storeSettings.lightTheme = lightThemeEnabled
}
}
}
@@ -126,11 +123,13 @@ StatusWindow {
StatusAppTwoPanelLayout {
id: mainPageView
function page(name) {
function page(name, fillPage) {
storeSettings.fillPage = fillPage ? true : false
viewLoader.source = Qt.resolvedUrl("./pages/" + name + "Page.qml");
}
function control(name) {
viewLoader.source = Qt.resolvedUrl("./controls/" + name + ".qml");
storeSettings.fillPage = false
}
leftPanel: Item {
@@ -275,6 +274,11 @@ StatusWindow {
selected: viewLoader.source.toString().includes(title)
onClicked: mainPageView.page(title);
}
StatusNavigationListItem {
title: "StatusListPicker"
selected: viewLoader.source.toString().includes(title)
onClicked: mainPageView.page(title);
}
StatusListSectionHeadline { text: "StatusQ.Popup" }
StatusNavigationListItem {
title: "StatusPopupMenu"
@@ -298,6 +302,11 @@ StatusWindow {
selected: viewLoader.source.toString().includes(title)
onClicked: mainPageView.page(title);
}
StatusNavigationListItem {
title: "StatusImageCropPanel"
selected: viewLoader.source.toString().includes(title)
onClicked: mainPageView.page(title, true);
}
}
}
}
@@ -305,8 +314,11 @@ StatusWindow {
rightPanel: Item {
id: rightPanel
anchors.fill: parent
ScrollView {
visible: !storeSettings.fillPage
anchors.fill: parent
anchors.topMargin: 64
contentHeight: (pageWrapper.height + pageWrapper.anchors.topMargin) * rootWindow.factor
contentWidth: (pageWrapper.width * rootWindow.factor)
clip: true
@@ -315,14 +327,16 @@ StatusWindow {
id: pageWrapper
width: rightPanel.width
anchors.top: parent.top
anchors.topMargin: 64
height: Math.max(rootWindow.height, viewLoader.height + 128)
scale: rootWindow.factor
Loader {
id: viewLoader
active: !storeSettings.fillPage
anchors.centerIn: parent
source: mainPageView.control("Icons")
source: storeSettings.selected.length === 0 ? mainPageView.control("Icons") : storeSettings.selected
onSourceChanged: {
storeSettings.selected = viewLoader.source
if (source.toString().includes("Icons")) {
item.iconColor = Theme.palette.primaryColor1;
}
@@ -330,6 +344,15 @@ StatusWindow {
}
}
}
Loader {
active: storeSettings.fillPage
anchors.fill: parent
anchors.topMargin: 64
visible: storeSettings.fillPage
clip: true
source: viewLoader.source
}
}
}
}
@@ -447,4 +470,11 @@ StatusWindow {
rootWindow.toggleFullScreen()
}
}
Settings {
id: storeSettings
property string selected: ""
property bool lightTheme: true
property bool fillPage: false
}
}
@@ -0,0 +1,94 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import QtGraphicalEffects 1.14
Item {
id: root
implicitWidth: mainLayout.implicitWidth
implicitHeight: mainLayout.implicitHeight
property var testControls: [ctrl1, ctrl2, ctrl3]
property bool globalStylePreferRound: true
ColumnLayout {
id: mainLayout
anchors.fill: parent
RowLayout {
ColumnLayout {
Text {
text: `AR: ${ctrl1.aspectRatio.toFixed(2)}`
}
StatusImageCropPanel {
id: ctrl1
Layout.fillWidth: true
Layout.fillHeight: true
source: "qrc:/demoapp/data/logo-test-image.png"
windowStyle: globalStylePreferRound ? StatusImageCrop.WindowStyle.Rounded : StatusImageCrop.WindowStyle.Rectangular
}
Text {
text: `AR: ${ctrl2.aspectRatio.toFixed(2)}`
}
StatusImageCropPanel {
id: ctrl2
Layout.fillWidth: true
Layout.fillHeight: true
source: "qrc:/demoapp/data/logo-test-image.png"
windowStyle: globalStylePreferRound ? StatusImageCrop.WindowStyle.Rectangular : StatusImageCrop.WindowStyle.Rounded
aspectRatio: 16/9
enableCheckers: true
}
}
ColumnLayout {
Text {
text: `AR: ${ctrl3.aspectRatio.toFixed(2)}`
}
StatusImageCropPanel {
id: ctrl3
Layout.fillWidth: true
Layout.fillHeight: true
source: "qrc:/demoapp/data/logo-test-image.png"
windowStyle: globalStylePreferRound ? StatusImageCrop.WindowStyle.Rounded : StatusImageCrop.WindowStyle.Rectangular
aspectRatio: 0.8
}
}
}
Shortcut {
sequence: StandardKey.ZoomIn
onActivated: {
for(let i in testControls) {
const c = testControls[i]
c.setCropRect(ctrl1.inflateRectBy(c.cropRect, -0.05))
}
}
}
Shortcut {
sequence: StandardKey.ZoomOut
onActivated: {
for(let i in testControls) {
const c = testControls[i]
c.setCropRect(ctrl1.inflateRectBy(c.cropRect, 0.05))
}
}
}
Shortcut {
sequences: ["Ctrl+W"]
onActivated: globalStylePreferRound = !globalStylePreferRound
}
}
}
+2 -2
View File
@@ -59,12 +59,13 @@ Column {
Item {
implicitWidth: 480
implicitHeight: 102
implicitHeight: 82
z: 100000
StatusSeedPhraseInput {
id: statusSeedInput
anchors.left: parent.left
anchors.right: parent.right
height: parent.height
textEdit.input.anchors.leftMargin: 16
textEdit.input.anchors.rightMargin: 16
textEdit.input.anchors.topMargin: 11
@@ -88,7 +89,6 @@ Column {
label: "Label"
charLimit: 30
errorMessage: "Error message"
input.clearable: true
input.valid: false
input.placeholderText: "Placeholder"
+81
View File
@@ -0,0 +1,81 @@
import QtQuick 2.0
import QtQuick.Layouts 1.14
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
import "../demoapp/data" 1.0
GridLayout {
id: root
columns: 1
rowSpacing: 150
GridLayout {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
rows: 4
columns: 2
rowSpacing: 170
columnSpacing: 150
z: 100
StatusListPicker {
id: languagePicker
z: 100
inputList: Models.languagePickerModel
placeholderSearchText: qsTr("Search Languages")
menuAlignment: StatusListPicker.MenuAlignment.Left
}
StatusListPicker {
id: languagePicker2
z: 100
inputList: Models.languageNoImagePickerModel
placeholderSearchText: qsTr("Search Languages")
menuAlignment: StatusListPicker.MenuAlignment.Center
}
StatusListPicker {
id: currencyPicker
inputList: Models.currencyPickerModel
placeholderSearchText: qsTr("Search Currencies")
multiSelection: true
}
StatusListPicker {
id: currencyPicker2
inputList: Models.currencyPickerModel2
placeholderSearchText: qsTr("Search Currencies")
multiSelection: true
printSymbol: true
enableSelectableItem: false
}
}
StatusBaseText {
id: pageDesc
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
height: 100
width: 500
text: "4 different configurations for the `StatusListPicker` component:\n
* Single selection. \n
* Single selection but dynamically changed to multiple selection (model provides multiple selected items).\n
* Multiple selection.\n
* Multiple selection and displayed name is the symbol + shortName\n"
color: Theme.palette.baseColor1
font.pixelSize: 15
}
// Outsite area
MouseArea {
height: root.height
width: root.width
onClicked: {
languagePicker.close()
languagePicker2.close()
currencyPicker.close()
currencyPicker2.close()
}
}
}
+8 -7
View File
@@ -15,10 +15,11 @@ Item {
width: parent.width
height: childrenRect.height
model: [
{"title":"anna.eth wants to verify your identity", "subTitle":"Provide the code in the letter I sent to you on February 1st.", "icon":"contact", "loading":false, "type":0,"url":""},
{"title":"Verification Request Sent", "subTitle":"", "icon":"checkmark-circle", "loading":false, "type":1,"url":""},
{"title":"Collectible is being minted...", "subTitle":"View on Etherscan", "icon":"", "loading":true, "type":0,"url":"http://google.com"},
{"title":"Contact request sent", "subTitle":"", "icon":"checkmark-circle", "loading":false, "type":1,"url":""}
{"title":"anna.eth wants to verify your identity", "subTitle":"Provide the code in the letter I sent to you on February 1st.", "icon":"contact", "loading":false, "type":0,"url":"", "duration":0},
{"title":"Verification Request Sent", "subTitle":"", "icon":"checkmark-circle", "loading":false, "type":1,"url":"", "duration":4000},
{"title":"Collectible is being minted...", "subTitle":"View on Etherscan", "icon":"", "loading":true, "type":0,"url":"http://google.com", "duration":0},
{"title":"Contact request sent", "subTitle":"", "icon":"checkmark-circle", "loading":false, "type":1,"url":"", "duration":4000},
{"title":"Test User", "subTitle":"Hello message...", "icon":"", "loading":false, "type":0,"url":"", "duration":4000}
]
delegate: StatusToastMessage {
primaryText: modelData.title
@@ -27,12 +28,12 @@ Item {
loading: modelData.loading
type: modelData.type
linkUrl: modelData.url
duration: modelData.duration
onLinkActivated: {
Qt.openUrlExternally(link);
}
//simulate open
Component.onCompleted: {
open = true;
onClose: {
console.warn("toast closed: ", modelData.title)
}
}
}
@@ -12,6 +12,6 @@ Column {
spacing: 8
StatusWalletColorSelect {
model: Theme.palette.accountColors
model: Theme.palette.userCustomizationColors
}
}
+13 -2
View File
@@ -16,7 +16,7 @@
<file>pages/StatusTabSwitchPage.qml</file>
<file>pages/StatusWalletColorButtonPage.qml</file>
<file>pages/StatusWalletColorSelectPage.qml</file>
<file>pages/StatusPasswordStrengthIndicatorPage.qml</file>
<file>pages/StatusPasswordStrengthIndicatorPage.qml</file>
<file>controls/Buttons.qml</file>
<file>controls/Controls.qml</file>
<file>controls/Icons.qml</file>
@@ -24,6 +24,8 @@
<file>controls/ListItems.qml</file>
<file>controls/Others.qml</file>
<file>controls/Popups.qml</file>
<file>demoapp/ChatChannelView.qml</file>
<file>demoapp/CreateChatView.qml</file>
<file>demoapp/DemoCommunityDetailModal.qml</file>
<file>demoapp/DemoContactRequestsModal.qml</file>
<file>demoapp/StatusAppChatView.qml</file>
@@ -31,5 +33,14 @@
<file>demoapp/StatusAppProfileSettingsView.qml</file>
<file>demoapp/data/Models.qml</file>
<file>demoapp/data/qmldir</file>
<file>demoapp/data/logo-test-image.png</file>
<file>DemoApp.qml</file>
<file>ThemeSwitch.qml</file>
<file>pages/StatusColorSelectorGridPage.qml</file>
<file>pages/StatusListPickerPage.qml</file>
<file>pages/StatusPinInputPage.qml</file>
<file>pages/StatusTagSelectorPage.qml</file>
<file>pages/StatusToastMessagePage.qml</file>
<file>pages/StatusWizardStepperPage.qml</file>
</qresource>
</RCC>
</RCC>
+1 -1
View File
@@ -32,7 +32,7 @@ Rectangle {
anchors.centerIn: parent
text: {
if (statusBadge.value > 99) {
return "99+";
return qsTr("99+");
}
return statusBadge.value;
}
+8 -5
View File
@@ -17,6 +17,7 @@ Column {
property string categoryId: ""
property var model: null
property bool draggableItems: false
property bool highlightItem: true
property alias statusChatListItems: statusChatListItems
@@ -90,23 +91,25 @@ Column {
}
StatusChatListItem {
id: statusChatListItem
opacity: dragSensor.active ? 0.0 : 1.0
originalOrder: model.position
chatId: model.itemId
categoryId: model.parentItemId? model.parentItemId : ""
name: model.name
icon.emoji: model.emoji
type: !!model.type ? model.type : StatusChatListItem.Type.CommunityChat
muted: model.muted
hasUnreadMessages: model.hasUnreadMessages
notificationsCount: model.notificationsCount
highlightWhenCreated: !!model.highlight
selected: model.active
icon.color: model.color
image.isIdenticon: model.isIdenticon
selected: (model.active && statusChatList.highlightItem)
icon.emoji: model.emoji
icon.color: !!model.color ? model.color : Theme.palette.userCustomizationColors[model.colorId]
image.isIdenticon: false
image.source: model.icon
ringSettings.ringSpecModel: model.colorHash
sensor.cursorShape: dragSensor.cursorShape
onClicked: {
@@ -14,10 +14,10 @@ Item {
implicitWidth: chatListsAndCategories.width
property StatusTooltipSettings categoryAddButtonToolTip: StatusTooltipSettings {
text: "Add channel inside category"
text: qsTr("Add channel inside category")
}
property StatusTooltipSettings categoryMenuButtonToolTip: StatusTooltipSettings {
text: "More"
text: qsTr("More")
}
property var model: []
@@ -48,7 +48,6 @@ Column {
popupMenuSlot.item.popup(mouse.x + 4, mouse.y + 6);
return
}
statusChatListCategory.opened = !opened;
}
}
onTitleClicked: statusChatListCategory.opened = !opened
@@ -39,21 +39,21 @@ StatusListItem {
id: addButton
icon.name: "add"
icon.width: 20
visible: statusChatListCategoryItem.showAddButton &&
visible: statusChatListCategoryItem.showAddButton &&
(statusChatListCategoryItem.highlighted ||
statusChatListCategoryItem.sensor.containsMouse)
onClicked: statusChatListCategoryItem.addButtonClicked(mouse)
tooltip.text: "Add channel inside category"
tooltip.text: qsTr("Add channel inside category")
},
StatusChatListCategoryItemButton {
id: menuButton
icon.name: "more"
icon.width: 21
visible: statusChatListCategoryItem.showMenuButton &&
visible: statusChatListCategoryItem.showMenuButton &&
(statusChatListCategoryItem.highlighted ||
statusChatListCategoryItem.sensor.containsMouse)
onClicked: statusChatListCategoryItem.menuButtonClicked(mouse)
tooltip.text: "More"
tooltip.text: qsTr("More")
},
StatusChatListCategoryItemButton {
id: toggleButton
+34 -35
View File
@@ -10,7 +10,7 @@ import StatusQ.Components 0.1
import StatusQ.Controls 0.1
Rectangle {
id: statusChatListItem
id: root
objectName: "chatItem"
property int originalOrder: -1
@@ -29,11 +29,10 @@ Rectangle {
width: 24
height: 24
color: Theme.palette.miscColor5
letterSize: emoji ? 11 : 15
emoji: ""
emojiSize: Emoji.size.verySmall
charactersLen: 1
charactersLen: root.type === StatusChatListItem.Type.OneToOneChat ? 2 : 1
}
property alias ringSettings: identicon.ringSettings
property int type: StatusChatListItem.Type.PublicChat
property bool highlighted: false
property bool highlightWhenCreated: false
@@ -79,16 +78,16 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: statusChatListItem.clicked(mouse)
onClicked: root.clicked(mouse)
StatusSmartIdenticon {
id: identicon
anchors.left: parent.left
anchors.leftMargin: 8
anchors.verticalCenter: parent.verticalCenter
image: statusChatListItem.image
icon: statusChatListItem.icon
name: statusChatListItem.name
image: root.image
icon: root.icon
name: root.name
}
StatusIcon {
@@ -98,21 +97,21 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 14
visible: statusChatListItem.type !== StatusChatListItem.Type.OneToOneChat
visible: root.type !== StatusChatListItem.Type.OneToOneChat
opacity: {
if (statusChatListItem.muted && !hoverHander.hovered && !statusChatListItem.highlighted) {
if (root.muted && !hoverHander.hovered && !root.highlighted) {
return 0.4
}
return statusChatListItem.hasUnreadMessages ||
statusChatListItem.notificationsCount > 0 ||
statusChatListItem.selected ||
statusChatListItem.highlighted ||
return root.hasUnreadMessages ||
root.notificationsCount > 0 ||
root.selected ||
root.highlighted ||
statusBadge.visible ||
hoverHander.hovered ? 1.0 : 0.7
}
icon: {
switch (statusChatListItem.type) {
switch (root.type) {
case StatusChatListItem.Type.PublicCat:
return Theme.palette.name == "light" ? "tiny/public-chat" : "tiny/public-chat-white"
break;
@@ -137,27 +136,27 @@ Rectangle {
anchors.rightMargin: 6
anchors.verticalCenter: parent.verticalCenter
text: (statusChatListItem.type === StatusChatListItem.Type.PublicChat &&
!statusChatListItem.name.startsWith("#") ?
"#" + statusChatListItem.name :
statusChatListItem.name)
text: (root.type === StatusChatListItem.Type.PublicChat &&
!root.name.startsWith("#") ?
"#" + root.name :
root.name)
elide: Text.ElideRight
color: {
if (statusChatListItem.muted && !hoverHander.hovered && !statusChatListItem.highlighted) {
if (root.muted && !hoverHander.hovered && !root.highlighted) {
return Theme.palette.directColor5
}
return statusChatListItem.hasUnreadMessages ||
statusChatListItem.notificationsCount > 0 ||
statusChatListItem.selected ||
statusChatListItem.highlighted ||
statusChatListItem.highlightWhenCreated ||
return root.hasUnreadMessages ||
root.notificationsCount > 0 ||
root.selected ||
root.highlighted ||
root.highlightWhenCreated ||
hoverHander.hovered ||
statusBadge.visible ? Theme.palette.directColor1 : Theme.palette.directColor4
}
font.weight: !statusChatListItem.muted &&
(statusChatListItem.hasUnreadMessages ||
statusChatListItem.notificationsCount > 0 ||
statusChatListItem.highlightWhenCreated ||
font.weight: !root.muted &&
(root.hasUnreadMessages ||
root.notificationsCount > 0 ||
root.highlightWhenCreated ||
statusBadge.visible) ? Font.Bold : Font.Medium
font.pixelSize: 15
}
@@ -170,18 +169,18 @@ Rectangle {
width: 14
opacity: mutedIconSensor.containsMouse ? 1.0 : 0.2
icon: Theme.palette.name === "light" ? "tiny/muted" : "tiny/muted-white"
visible: statusChatListItem.muted
visible: root.muted
MouseArea {
id: mutedIconSensor
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: statusChatListItem.unmute()
onClicked: root.unmute()
}
StatusToolTip {
text: "Unmute"
text: qsTr("Unmute")
visible: mutedIconSensor.containsMouse
}
}
@@ -193,11 +192,11 @@ Rectangle {
anchors.right: parent.right
anchors.rightMargin: 8
color: statusChatListItem.muted ? Theme.palette.primaryColor2 : Theme.palette.primaryColor1
color: root.muted ? Theme.palette.primaryColor2 : Theme.palette.primaryColor1
border.width: 4
border.color: color
value: statusChatListItem.notificationsCount
visible: statusChatListItem.notificationsCount > 0
value: root.notificationsCount
visible: root.notificationsCount > 0
}
}
}
+4 -4
View File
@@ -58,7 +58,7 @@ Rectangle {
onClicked: statusChatToolBar.searchButtonClicked()
// initializing the tooltip
tooltip.text: "Search"
tooltip.text: qsTr("Search")
tooltip.orientation: StatusToolTip.Orientation.Bottom
tooltip.y: parent.height + 12
}
@@ -72,7 +72,7 @@ Rectangle {
onClicked: statusChatToolBar.membersButtonClicked()
// initializing the tooltip
tooltip.text: "Members"
tooltip.text: qsTr("Members")
tooltip.orientation: StatusToolTip.Orientation.Bottom
tooltip.y: parent.height + 12
}
@@ -87,7 +87,7 @@ Rectangle {
// initializing the tooltip
tooltip.visible: !!tooltip.text && menuButton.hovered && !popupMenuSlot.item.opened
tooltip.text: "More"
tooltip.text: qsTr("More")
tooltip.orientation: StatusToolTip.Orientation.Bottom
tooltip.y: parent.height + 12
@@ -141,7 +141,7 @@ Rectangle {
type: StatusFlatRoundButton.Type.Secondary
// initializing the tooltip
tooltip.text: "Activity"
tooltip.text: qsTr("Activity")
tooltip.orientation: StatusToolTip.Orientation.Bottom
tooltip.y: parent.height + 12
@@ -12,6 +12,7 @@ Rectangle {
property alias additionalText: additionalText.text
property alias button: button
property alias expandableComponent: expandableRegion.sourceComponent
property alias expandableItem: expandableRegion.item
property int type: StatusExpandableItem.Type.Primary
property bool expandable: true
@@ -185,7 +186,7 @@ Rectangle {
anchors.leftMargin: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? 48 : 0
anchors.right: parent.right
anchors.rightMargin: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? 16 : 0
active: false
visible: false
}
onExpandedChanged: {
@@ -202,13 +203,13 @@ Rectangle {
name: "EXPANDED"
PropertyChanges {target: expandImage; icon: "chevron-up"}
PropertyChanges {target: statusExpandableItem; height: 82 + expandableRegion.height + 22}
PropertyChanges {target: expandableRegion; active: true}
PropertyChanges {target: expandableRegion; visible: true}
},
State {
name: "COLLAPSED"
PropertyChanges {target: expandImage; icon: "chevron-down"}
PropertyChanges {target: statusExpandableItem; height: 82}
PropertyChanges {target: expandableRegion; active: false}
PropertyChanges {target: expandableRegion; visible: false}
}
]
@@ -0,0 +1,281 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
/*!
\qmltype StatusImageCropPanel
\inherits Item
\inqmlmodule StatusQ.Components
\since StatusQ.Components 0.1
\brief Draw a crop-window onto an image and allows manipulating the position of the crop-window. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
Adds mouse pan and zoom functionality on top of StatusImageCrop functionality. Also adds small
practical features and optimizes drawing by minimizing StatusImageCrop area
\sa StatusImageCrop for more details
Example of how to use it:
\qml
StatusImageCropPanel {
width: 400
height: 200
source: "qrc:/demoapp/data/logo-test-image.png"
windowStyle: StatusImageCrop.WindowStyle.Rectangular
Component.onCompleted: setCropRect(Qt.rect(10, 0, sourceSize.width - 20, sourceSize.height))
}
\endqml
For a list of components available see StatusQ.
*/
Item {
id: root
implicitWidth: mainLayout.implicitWidth
implicitHeight: mainLayout.implicitHeight
/*!
\qmlproperty real StatusImageCrop::aspectRatio
Initial aspect-ratio of the crop-window
*/
property real aspectRatio: 1
/*!
\qmlproperty bool StatusImageCrop::interactive
If true allows user to interact with the image. Set to false for previewing existing crop-data
*/
property bool interactive: true
/*!
\qmlproperty bool StatusImageCrop::margins
Space to keep around the control borders and crop area
*/
property int margins: 10
/*!
\qmlproperty url StatusImageCropPanel::source
\sa StatusImageCrop::source
*/
/*required*/ property alias source: cropEditor.source
/*!
\qmlproperty WindowStyle StatusImageCropPanel::windowStyle
\sa StatusImageCrop::windowStyle
*/
property alias windowStyle: cropEditor.windowStyle
/*!
\qmlproperty int StatusImageCropPanel::radius
\sa StatusImageCrop::radius
*/
property alias radius: cropEditor.radius
/*!
\qmlproperty color StatusImageCropPanel::wallColor
\sa StatusImageCrop::wallColor
*/
property alias wallColor: cropEditor.wallColor
/*!
\qmlproperty real StatusImageCropPanel::wallTransparency
\sa StatusImageCrop::wallTransparency
*/
property alias wallTransparency: cropEditor.wallTransparency
/*!
\qmlproperty rect StatusImageCropPanel::cropRect
\sa StatusImageCrop::cropRect
*/
property alias cropRect: cropEditor.cropRect
/*!
\qmlproperty rect StatusImageCropPanel::cropRect
\sa StatusImageCrop::cropRect
*/
readonly property alias cropWindow: cropEditor.cropWindow
/*!
\qmlproperty real StatusImageCrop::scrollZoomFactor
How fast is image scaled (zoomed) when using mouse scroll
*/
property real scrollZoomFactor: 0.5
/*!
\qmlproperty bool StatusImageCropPanel::enableCheckers
Shows helper guiding checkers pattern where image is not covering
*/
property bool enableCheckers: root.interactive
/*!
\qmlproperty size StatusImageCropPanel::sourceSize
\sa StatusImageCrop::sourceSize
*/
property alias sourceSize: cropEditor.sourceSize
/*
\qmlmethod StatusImageCropPanel::setCropRect(rect)
\sa StatusImageCrop::cropRect
*/
function setCropRect(newRect) {
cropEditor.setCropRect(newRect)
aspectRatio = cropEditor.aspectRatio
}
QtObject {
id: d
function updateAspectRatio(newAR) {
// Keep width and adjust height
const eW = cropEditor.cropRect.width
const w = (eW <= 0) ? cropEditor.sourceSize.width : eW
const h = w/newAR
const c = (eW <= 0)
? Qt.point(cropEditor.sourceSize.width/2, cropEditor.sourceSize.height/2)
: Qt.point(cropEditor.cropRect.x + w/2, cropEditor.cropRect.y + cropEditor.cropRect.height/2)
const nR = Qt.rect(c.x - w/2, c.y-h/2, w, h)
cropEditor.setCropRect(nR)
}
}
Component.onCompleted: d.updateAspectRatio(root.aspectRatio)
onAspectRatioChanged: d.updateAspectRatio(root.aspectRatio)
onSourceSizeChanged: d.updateAspectRatio(root.aspectRatio)
ColumnLayout {
id: mainLayout
anchors.fill: parent
Item {
id: cropSpaceItem
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
Rectangle {
id: leftOverlay
anchors.left: parent.left
anchors.top: parent.top
anchors.right: cropEditor.left
anchors.bottom: parent.bottom
color: wallColor
opacity: wallTransparency
z: cropEditor.z + 1
}
Rectangle {
id: topOverlay
anchors.left: leftOverlay.right
anchors.top: parent.top
anchors.right: rightOverlay.left
anchors.bottom: cropEditor.top
color: wallColor
opacity: wallTransparency
z: cropEditor.z + 1
}
StatusImageCrop {
id: cropEditor
anchors.centerIn: parent
width: aspectRatio < cropSpaceItem.width/cropSpaceItem.height ? cropSpaceItem.height * aspectRatio : cropSpaceItem.width - root.margins * 2
height: aspectRatio < cropSpaceItem.width/cropSpaceItem.height ? cropSpaceItem.height - root.margins * 2 : cropSpaceItem.width / aspectRatio
}
Rectangle {
id: rightOverlay
anchors.left: cropEditor.right
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: parent.bottom
color: wallColor
opacity: wallTransparency
z: cropEditor.z + 1
}
Rectangle {
id: bottomOverlay
anchors.left: leftOverlay.right
anchors.top: cropEditor.bottom
anchors.right: rightOverlay.left
anchors.bottom: parent.bottom
color: wallColor
opacity: wallTransparency
z: cropEditor.z + 1
}
// Checkers
Canvas {
visible: root.enableCheckers
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
for(let xI = 0; xI < Math.ceil(width/10); xI++) {
for(let yI = 0; yI < Math.ceil(height/10); yI++) {
ctx.fillStyle = (xI % 2) === (yI % 2) ? "#FFFFFE" : "#DBDBDB"
ctx.fillRect(xI * 10, yI * 10, 10, 10)
}
}
}
z: cropEditor.z - 1
}
// Drag and zoom
MouseArea {
anchors.fill: parent
enabled: root.interactive
property var lastDragPoint: null
onReleased: lastDragPoint = null
onMouseXChanged: updateDrag(Qt.point(mouse.x, mouse.y))
onMouseYChanged: updateDrag(Qt.point(mouse.x, mouse.y))
onWheel: {
const delta = wheel.angleDelta.y / 120
cropEditor.setCropRect(cropEditor.getZoomRect(cropEditor.zoomScale + delta * root.scrollZoomFactor))
}
function moveRect(r /*rect*/, delta /*real*/) {
return Qt.rect(r.x + delta.x, r.y + delta.y, r.width, r.height)
}
function scaleSize(sz /*size*/, s /*size*/) {
return Qt.point(sz.width * s, sz.height * s)
}
function updateDrag(p) {
let delta = (lastDragPoint ? Qt.size(lastDragPoint.x - p.x, lastDragPoint.y - p.y) : Qt.size(0, 0))
delta = scaleSize(delta, 1/cropEditor.scrToImgScale)
cropEditor.setCropRect(moveRect(cropEditor.cropRect, delta))
lastDragPoint = p
}
}
}
RowLayout {
visible: root.interactive
StatusIcon {
icon: "remove-circle"
Layout.preferredWidth: 20
Layout.preferredHeight: 20
}
StatusSlider {
Layout.fillWidth: true
Layout.topMargin: 20
Layout.bottomMargin: 25
Layout.alignment: Qt.AlignVCenter
enabled: root.interactive
from: cropEditor.minZoomScale
to: cropEditor.maxZoomScale
value: cropEditor.zoomScale
live: false
onMoved: cropEditor.setCropRect(cropEditor.getZoomRect(valueAt(visualPosition)))
}
StatusIcon {
icon: "add-circle"
Layout.preferredWidth: 20
Layout.preferredHeight: 20
}
}
}
}
@@ -9,7 +9,7 @@ Rectangle {
property alias identiconText: identiconText
property string name
property string emoji
property string emojiSize
property string emojiSize: Emoji.size.small
property int letterSize: 21
property int charactersLen: 1
@@ -29,14 +29,14 @@ Rectangle {
font.weight: Font.Bold
font.pixelSize: root.letterSize
color: Qt.rgba(255, 255, 255, 0.7)
color: d.luminance(root.color) > 0.5 ? Qt.rgba(0, 0, 0, 0.5) : Qt.rgba(255, 255, 255, 0.7)
text: {
if (emoji) {
if(Utils.isHtml(emoji))
return emoji
else
return Emoji.parse(emoji, emojiSize || undefined)
return Emoji.parse(root.emoji, emojiSize)
}
const shift = (root.name.charAt(0) === "#") ||
@@ -45,5 +45,15 @@ Rectangle {
return root.name.substring(shift, shift + charactersLen).toUpperCase()
}
}
QtObject {
id: d
function luminance(color) {
let r = Math.pow(color.r, 2.2) * 0.2126
let g = Math.pow(color.g, 2.2) * 0.7151
let b = Math.pow(color.b, 2.2) * 0.0721
return r + g + b
}
}
}
+41 -17
View File
@@ -14,17 +14,19 @@ Rectangle {
property string title: ""
property string titleAsideText: ""
property string subTitle: ""
property string tertiaryTitle: ""
property string tertiaryTitle: ""
property string label: ""
property real leftPadding: 16
property real rightPadding: 16
property bool enabled: true
property bool highlighted: false
property bool propagateTitleClicks: true
property bool propagateTitleClicks: true
property int type: StatusListItem.Type.Primary
property list<Item> components
property var bottomModel: []
property Component bottomDelegate
property var tagsModel: []
property Component tagsDelegate
property StatusIconSettings icon: StatusIconSettings {
height: isLetterIdenticon ? 40 : 20
@@ -71,6 +73,7 @@ Rectangle {
property alias statusListItemTertiaryTitle: statusListItemTertiaryTitle
property alias statusListItemComponentsSlot: statusListItemComponentsSlot
property alias statusListItemTagsSlot: statusListItemTagsSlot
property alias statusListItemInlineTagsSlot: statusListItemTagsSlotInline
signal clicked(string itemId)
signal titleClicked(string titleId)
@@ -136,7 +139,8 @@ Rectangle {
name: statusListItem.title
active: statusListItem.icon.isLetterIdenticon ||
!!statusListItem.icon.name ||
!!statusListItem.image.source.toString()
!!statusListItem.image.source.toString() ||
!!statusListItem.icon.emoji
badge.border.color: statusListItem.color
ringSettings: statusListItem.ringSettings
}
@@ -165,26 +169,34 @@ Rectangle {
text: statusListItem.title
font.pixelSize: 15
height: visible ? contentHeight : 0
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
elide: Text.ElideRight
anchors.left: parent.left
anchors.right: !statusListItem.titleAsideText && !titleIconsRow.sourceComponent ? parent.right : undefined
anchors.top: bottomModel.length === 0 ? undefined: parent.top
anchors.topMargin: bottomModel.length === 0 ? undefined : 20
width: Math.min(implicitWidth, parent.width)
color: {
if (!statusListItem.enabled) {
return Theme.palette.baseColor1
}
switch (statusListItem.type) {
case StatusListItem.Type.Primary:
return Theme.palette.directColor1
case StatusListItem.Type.Secondary:
return Theme.palette.primaryColor1
case StatusListItem.Type.Danger:
return Theme.palette.dangerColor1
}
if (!statusListItem.enabled) {
return Theme.palette.baseColor1
}
switch (statusListItem.type) {
case StatusListItem.Type.Primary:
return Theme.palette.directColor1
case StatusListItem.Type.Secondary:
return Theme.palette.primaryColor1
case StatusListItem.Type.Danger:
return Theme.palette.dangerColor1
}
}
StatusToolTip {
id: statusListItemTitleTooltip
text: statusListItemTitle.text
delay: 0
visible: statusListItemTitle.truncated && statusListItemTitleMouseArea.containsMouse
}
MouseArea {
id: statusListItemTitleMouseArea
anchors.fill: parent
enabled: statusListItem.enabled
cursorShape: sensor.enabled && containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor
@@ -247,7 +259,19 @@ Rectangle {
width: contentItem.width
implicitHeight: visible ? 22 : 0
}
Row {
id: statusListItemTagsSlotInline
anchors.top: statusListItemTertiaryTitle.bottom
width: parent.width
spacing: 10
clip: true
Repeater {
model: tagsModel
delegate: tagsDelegate
}
}
}
Row {
+392
View File
@@ -0,0 +1,392 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtGraphicalEffects 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
/*!
\qmltype StatusListPicker
\inherits Item
\inqmlmodule StatusQ.Components
\since StatusQ.Components 0.1
\brief It is a combination of StatusPickerButton and a drop-down list component that provides a way of presenting a list of selectable options to the user. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
The \c StatusListPicker is populated with a data model. The data model is commonly a JavaScript array or a ListModel object.
StatusListPicker can be made as a single or multiple options picker by setting its StatusListPicker::multiSelection property properly or will be auto-set if the model contains / provides more than one selected items.
The StatusPickerButton text holds the text of the current item selected in the list picker. If there is more than one item selected, a string composed will be displayed.
The drop-down list incorporates a searcher by the following model roles: `name` or / and `shortName`.
NOTE: Make sure to set the appropriate z-index when instantiating the component in order to place it on top of your view in order to display the drop-down data properly.
Make sure to position the component inside its parent also when it is spanned in order to achieve a better user experience.
Example of how the component looks like:
\image status_list_picker.png
Example of how to use it:
\qml
StatusListPicker {
id: currencyPicker
inputList: Models.currencyPickerModel
searchText: qsTr("Search Language")
multiSelection: true
printSymbol: true
}
\endqml
For a list of components available see StatusQ.
*/
Item {
id: root
/*!
\qmlproperty string StatusListPicker::inputList
This property holds the data that will be populated in the list picker.
NOTE: This model property should not change so it is an in / out property where the selected role will be dynamically changed according to user actions.
Here an example of the model roles expected:
\qml
inputList: ListModel {
ListElement {
key: 0
name: "United States Dollar"
shortName: "USD"
symbol: "$"
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
category: "fiat"
selected: true
}
ListElement {
key: 1
name: "British Pound"
shortName: "GBP"
symbol: "£"
imageSource: "../../assets/twemoji/26x26/1f4b5.png"
category: "fiat"
selected: false
}
}
\endqml
*/
property ListModel inputList: ListModel { }
/*!
\qmlproperty string StatusListPicker::searchText
This property holds the search text the searcher input displays by default.
*/
property string searchText: ""
/*!
\qmlproperty string StatusListPicker::placeholderSearchText
This property holds the placeholder text the searcher input displays by default.
*/
property string placeholderSearchText: qsTr("Search")
/*!
\qmlproperty string StatusListPicker::multiSelection
This property holds whether the list picker is a single or a multiple picker option by displaying a StatusRadioButton or StatusCheckBox instead.
*/
property bool multiSelection: false
/*!
\qmlproperty string StatusListPicker::printSymbol
This property holds whether the selected items will display a composition of the role `symbol` in the StatusPickerButton text or just only the role `shortName`.
*/
property bool printSymbol: false
/*!
\qmlproperty string StatusListPicker::maxPickerHeight
This property holds the maximum drop-down list height allowed. The drop-down list height will be set as the minimum value between the list content height and the maxPickerHeight property.
*/
property int maxPickerHeight: 718
/*!
\qmlproperty string StatusListPicker::enableSelectableItem
This property holds if the item in the list will be highlighted when hovering and clickable in its complete area or just not highlighted and able to be selected by clicking only the checbox or radiobutton area.
*/
property bool enableSelectableItem: true
/*!
\qmlproperty string StatusListPicker::menuAlignment
This property holds the allignment of the menu in terms of the button
*/
property int menuAlignment: StatusListPicker.MenuAlignment.Right
/*!
\qmlproperty enum StatusListPicker::MenuAlignment
This property holds the allignment of the menu in terms of the button
values can be Left, Right or Center
*/
enum MenuAlignment {
Left,
Right,
Center
}
/*
\qmlmethod StatusListPicker::close()
It can be used to force to close the drop-down picker list whenever the consumer needs it. For example by adding an outside MouseArea to close the picker when user clicks outsite the component:
\qml
// Outsite area
MouseArea {
height: root.height
width: root.width
onClicked: { currencyPicker.close() }
}
\endqml
*/
function close() {
picker.visible = false
// Reset searcher:
root.searchText = ""
}
/*!
\qmlsignal StatusListPicker::itemPickerChanged(string key, bool selected)
This signal is emitted when an item changes its selected value.
*/
signal itemPickerChanged(string key, bool selected)
QtObject {
id: d
property var filteredModel: ListModel { }
// Used to set up component and the needed private properties
function initialize() {
if(filteredModel.count === 0) {
// It is necessary to load the model, not loaded yet!
var selected = 0
var selectionText = ""
for(var i = 0; i < root.inputList.count; i++) {
var item = root.inputList.get(i)
d.filteredModel.append(item)
if(item.selected) selected++;
}
// If the given model has more than one selected elements, the behaviour of the list picker will be as a multiple selector with checkboxes although it is
// set in the radiobutton mode. Radiobutton mode is only for mutial-exclusion.
if(selected > 1)
multiSelection = true
// Update selected items text:
d.getSelectedItemsText()
}
}
// Used to update model elements given a specific text by filtering them for its `name` and / or `shortName`.
function applyFilter(text) {
const input = text.toLowerCase()
filteredModel.clear()
for(var i = 0; i < root.inputList.count; i++) {
let item = root.inputList.get(i)
if(item.name.toLowerCase().includes(input) || item.shortName.toLowerCase().includes(input))
filteredModel.append(item)
}
}
function formatSymbolShortNameText(symbol, shortName) {
var formattedText = ""
if(root.printSymbol && symbol)
formattedText = symbol + shortName
else
formattedText = shortName
return formattedText
}
function getSelectedItemsText() {
var res = ""
for(var i = 0; i < root.inputList.count; i++) {
var item = root.inputList.get(i)
if(item.selected) {
if(res != "")
res += ", "
res += d.formatSymbolShortNameText(item.symbol, item.shortName)
}
}
return res
}
// Used to update the base input list model with the new selected property
function updateInputListModel(key, selected, isSingleSelection) {
// If it is a single selection we must ensure that when a new key is selected others are cleared.
// It must be done manually because as there is the option of filterning, the model visualized changes
// and it could be possible to have a filtered list without an element selected (but in the base model
// it is) so, the binding to not selected will not be executed, as in the current filtered model
// there are no changes to update, just only the selected one.
for(var i = 0; i < root.inputList.count; i++) {
if(root.inputList.get(i).key === key) {
root.inputList.get(i).selected = selected
}
else if(isSingleSelection && selected) {
// Clear all the list
root.inputList.get(i).selected = false
}
}
}
}
width: 110
height: 38
Component.onCompleted: d.initialize()
StatusPickerButton {
id: btn
anchors.fill: parent
bgColor: Theme.palette.primaryColor3
contentColor: Theme.palette.primaryColor1
text: picker.selectedItemsText
textPixelSize: 13
type: StatusPickerButton.Type.Down
onClicked: {
picker.visible = !picker.visible
// Restart list position:
content.positionViewAtBeginning()
content.forceActiveFocus()
}
}
Rectangle {
id: picker
property string selectedItemsText: ""
width: content.itemWidth
height: Math.min(content.contentHeight + content.anchors.topMargin + content.anchors.bottomMargin, root.maxPickerHeight)
anchors.left: root.menuAlignment === StatusListPicker.MenuAlignment.Left ? btn.left : undefined
anchors.right: root.menuAlignment === StatusListPicker.MenuAlignment.Right ? btn.right : undefined
anchors.horizontalCenter: root.menuAlignment === StatusListPicker.MenuAlignment.Center ? btn.horizontalCenter : undefined
anchors.top: btn.bottom
anchors.topMargin: 4
visible: false
color: Theme.palette.statusPopupMenu.backgroundColor
radius: 8
layer.enabled: true
layer.effect: DropShadow {
source: picker
horizontalOffset: 0
verticalOffset: 4
radius: 12
samples: 25
spread: 0.2
color: Theme.palette.dropShadow
}
ListView {
id: content
property int itemHeight: 40
property int itemWidth: 360
model: d.filteredModel
width: itemWidth
anchors.top: parent.top
anchors.topMargin: 8
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
currentIndex: -1
clip: true
headerPositioning: ListView.OverlayHeader
header: Rectangle {
id: header
width: content.itemWidth
height: searchInput.height + 24
color: Theme.palette.statusPopupMenu.backgroundColor
z: 3 // Above delegate (z=1) and above section.delegate (z = 2)
StatusBaseInput {
id: searchInput
implicitHeight: 36
width: content.itemWidth - 2 * 18
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
topPadding: 0
bottomPadding: 0
placeholderText: root.placeholderSearchText
text: root.searchText
icon.name: "search"
onTextChanged: {
d.applyFilter(text)
root.searchText = text
}
}
}// End of search input item
delegate: StatusItemPicker {
width: content.itemWidth
height: content.itemHeight
color: mouseArea.containsMouse ? Theme.palette.baseColor4 : "transparent"
image: StatusImageSettings {
source: model.imageSource ? model.imageSource : ""
width: 15
height: 15
isIdenticon: false
}
name: model.name
shortName: model.shortName
selectorType: root.multiSelection ? StatusItemPicker.SelectorType.CheckBox : StatusItemPicker.SelectorType.RadioButton
selected: model.selected
radioGroup: radioBtnGroup
onCheckedChanged: {
d.updateInputListModel(model.key, checked, selectorType === StatusItemPicker.SelectorType.RadioButton)
if(selectorType === StatusItemPicker.SelectorType.RadioButton && checked) {
// Update selected item text
picker.selectedItemsText = d.formatSymbolShortNameText(model.symbol, model.shortName)
}
else {
// Update selected items text (multiple selection, text chain).
picker.selectedItemsText = d.getSelectedItemsText()
}
// Used to notify selected property changes in the specific item picker.
itemPickerChanged(model.key, checked)
}
MouseArea {
id: mouseArea
enabled: root.enableSelectableItem
anchors.fill: parent
cursorShape: root.enableSelectableItem ? Qt.PointingHandCursor : Qt.ArrowCursor
hoverEnabled: true
onClicked: {
if(selectorType === StatusItemPicker.SelectorType.RadioButton) {
// Just only allow to select, not unselect in case of single selection (radiobutton)
if(!selected) selected = !selected
}
else
// In case of multiple selections you can both select and unselect.
selected = !selected
}
}
}
section.property: "category"
section.criteria: ViewSection.FullString
section.delegate: Item {
width: content.itemWidth
height: content.itemHeight
StatusBaseText {
anchors.leftMargin: 18
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: section
color: Theme.palette.baseColor1
font.pixelSize: 15
elide: Text.ElideRight
}
}// End of Category item
onActiveFocusChanged: { if(!activeFocus) root.close() }
// Not visual element to control mutual-exclusion of radiobuttons that are not sharing the same parent (inside list view)
ButtonGroup {
id: radioBtnGroup
}
}// End of Content
}// End of Rectangle picker
}
+80 -16
View File
@@ -2,36 +2,100 @@ import QtQuick 2.0
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
/*!
\qmltype StatusMemberListItem
\inherits StatusListItem
\inqmlmodule StatusQ.Components
\since StatusQ.Components 0.1
\brief It is a list item with a specific format to display members of a community or chat. Inherits from \c StatusListItem.
The \c StatusMemberListItem is a clickable / hovering list item with a specific format to display members of a community or chat.
It displays the member avatar, trust and mutual contact indicators, nick name, user name, chat key and information about the member connectivity.
Example of how the control looks like:
\image status_member_list_item.png
Example of how to use it:
\qml
StatusMemberListItem {
nickName: "carmen.eth"
isOnline: false
trustIndicator: StatusContactVerificationIcons.TrustedType.Untrustworthy
}
StatusMemberListItem {
nickName: "This girl I know from work"
userName: "annabelle"
isOnline: true
}
\endqml
For a list of components available see StatusQ.
*/
StatusListItem {
id: root
/*!
\qmlproperty string StatusMemberListItem::nickName
This property holds the nick name of the member represented.
*/
property string nickName: ""
/*!
\qmlproperty string StatusMemberListItem::userName
This property holds the user name of the member represented.
*/
property string userName: ""
/*!
\qmlproperty string StatusMemberListItem::chatKey
This property holds the chat key of the member represented.
*/
property string chatKey: ""
/*!
\qmlproperty string StatusMemberListItem::isMutualContact
This property holds if the member represented is a mutual contact.
*/
property bool isMutualContact: false
/*!
\qmlproperty string StatusMemberListItem::trustIndicator
This property holds the trust indicator of the member represented. Possible values are:
\list
\li StatusContactVerificationIcons.TrustedType.None
\li StatusContactVerificationIcons.TrustedType.Verified
\li StatusContactVerificationIcons.TrustedType.Untrustworthy
\endlist
*/
property var trustIndicator: StatusContactVerificationIcons.TrustedType.None
/*!
\qmlproperty string StatusMemberListItem::isOnline
This property holds the connectivity status of the member represented.
*/
property bool isOnline: false
// Subtitle composition:
function composeSubtitile() {
var compose = ""
if(root.userName !== "")
compose = "(" + root.userName + ")"
QtObject {
id: d
if(compose !== "" && root.chatKey !== "")
// Composition
compose += " • " + composeShortKeyChat(root.chatKey)
// Subtitle composition:
function composeSubtitile() {
var compose = ""
if(root.userName !== "")
compose = "(" + root.userName + ")"
else if(root.chatKey !== "")
compose = composeShortKeyChat(root.chatKey)
if(compose !== "" && root.chatKey !== "")
// Composition
compose += " • " + composeShortKeyChat(root.chatKey)
return compose
}
else if(root.chatKey !== "")
compose = composeShortKeyChat(root.chatKey)
// Short keychat composition:
function composeShortKeyChat(chatKey) {
return chatKey.substring(0, 5) + "..." + chatKey.substring(chatKey.length - 3)
return compose
}
// Short keychat composition:
function composeShortKeyChat(chatKey) {
return chatKey.substring(0, 5) + "..." + chatKey.substring(chatKey.length - 3)
}
}
// root object settings:
@@ -40,7 +104,7 @@ StatusListItem {
isMutualContact: root.isMutualContact
trustIndicator: root.trustIndicator
}
subTitle: composeSubtitile()
subTitle: d.composeSubtitile()
statusListItemSubTitle.font.pixelSize: 10
icon.isLetterIdenticon: !root.image.source.toString()
statusListItemIcon.badge.visible: true
@@ -32,7 +32,7 @@ StatusListItem {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: statusNavigationListItem.clicked(mouse)
onClicked: statusNavigationListItem.clicked(itemId, mouse)
}
components: [
@@ -15,7 +15,6 @@ Loader {
property StatusIconSettings icon: StatusIconSettings {
width: 40
height: 40
charactersLen: 1
}
property StatusImageSettings image: StatusImageSettings {
@@ -25,7 +24,7 @@ Loader {
property StatusIdenticonRingSettings ringSettings: StatusIdenticonRingSettings {
initalAngleRad: 0
ringPxSize: 1.5
ringPxSize: Math.max(1.5, statusSmartIdenticon.image.width / 24.0)
distinctiveColors: Theme.palette.identiconRingColors
}
@@ -87,7 +86,6 @@ Loader {
emoji: statusSmartIdenticon.icon.emoji
emojiSize: statusSmartIdenticon.icon.emojiSize
letterSize: statusSmartIdenticon.icon.letterSize
identiconText.color: statusSmartIdenticon.icon.textColor
charactersLen: statusSmartIdenticon.icon.charactersLen
}
}
+161 -36
View File
@@ -6,29 +6,122 @@ import QtGraphicalEffects 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
/*!
\qmltype StatusTagSelector
\inherits Item
\inqmlmodule StatusQ.Components
\since StatusQ.Components 0.1
\brief Displays a tag selector component together with a list of where to select and add the tags from.
Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
The \c StatusTagSelector displays a list of asorted elements together with a text input where tags are added. As the user
types some text, the list elements are filtered and if the user selects any of those a new tag is created.
For example:
\qml
StatusTagSelector {
width: 650
height: 44
anchors.centerIn: parent
namesModel: ListModel {
ListElement {
publicId: "0x0"
name: "Maria"
icon: ""
isIdenticon: false
onlineStatus: 3
}
ListElement {
publicId: "0x1"
name: "James"
icon: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
isIdenticon: false
onlineStatus: 1
}
ListElement {
publicId: "0x2"
name: "Paul"
icon: ""
isIdenticon: false
onlineStatus: 2
}
}
toLabelText: qsTr("To: ")
warningText: qsTr("USER LIMIT REACHED")
}
\endqml
\image status_tag_selector.png
For a list of components available see StatusQ.
*/
Item {
id: root
implicitWidth: 448
implicitHeight: 44 + ((userListView.count > 0) ? 44 + ((((userListView.count * 64) > root.maxHeight)
? root.maxHeight : (userListView.count * 64))) :0)
implicitHeight: (104 + contactsLabel.height + contactsLabel.anchors.topMargin + (userListView.model.count * 64)) > root.maxHeight ? root.maxHeight :
(104 + contactsLabel.height + contactsLabel.anchors.topMargin + (userListView.model.count * 64))
property real maxHeight
/*!
\qmlproperty real StatusTagSelector::maxHeight
This property holds the maximum height of the component.
*/
property real maxHeight: (488 + contactsLabel.height + contactsLabel.anchors.topMargin) //default min
/*!
\qmlproperty alias StatusTagSelector::textEdit
This property holds a reference to the TextEdit component.
*/
property alias textEdit: edit
/*!
\qmlproperty alias StatusTagSelector::text
This property holds a reference to the TextEdit's text property.
*/
property alias text: edit.text
/*!
\qmlproperty string StatusTagSelector::warningText
This property sets the warning text.
*/
property string warningText: ""
/*!
\qmlproperty string StatusTagSelector::toLabelText
This property sets the 'to' label text.
*/
property string toLabelText: ""
/*!
\qmlproperty string StatusTagSelector::listLabel
This property sets the elements list label text.
*/
property string listLabel: ""
/*!
\qmlproperty int StatusTagSelector::nameCountLimit
This property sets the tags count limit.
*/
property int nameCountLimit: 5
/*!
\qmlproperty ListModel StatusTagSelector::sortedList
This property holds the sorted list model.
*/
property ListModel sortedList: ListModel { }
/*!
\qmlproperty ListModel StatusTagSelector::namesModel
This property holds the asorted names model.
*/
property ListModel namesModel: ListModel { }
/*!
\qmlmethod
This function is used to find an entry in a model.
*/
function find(model, criteria) {
for (var i = 0; i < model.count; ++i) if (criteria(model.get(i))) return model.get(i);
return null;
}
/*!
\qmlmethod
This function is used to insert a new tag.
*/
function insertTag(name, id) {
if (!find(namesModel, function(item) { return item.publicId === id }) && namesModel.count < root.nameCountLimit) {
namesModel.insert(namesModel.count, {"name": name, "publicId": id});
@@ -37,15 +130,20 @@ Item {
}
}
/*!
\qmlmethod
This function is used to sort the source model.
*/
function sortModel(inputModel) {
sortedList.clear();
if (text !== "") {
for (var i = 0; i < inputModel.count; i++ ) {
var entry = inputModel.get(i);
if (entry.name.toLowerCase().includes(text.toLowerCase())) {
sortedList.insert(sortedList.count, {"publicId": entry.publicId, "name": entry.name,
"icon": entry.icon, "isIdenticon": entry.isIdenticon,
"onlineStatus": entry.onlineStatus});
if (entry.name.toLowerCase().includes(text.toLowerCase()) &&
!find(namesModel, function(item) { return item.name === entry.name })) {
sortedList.append({"publicId": entry.publicId, "name": entry.name,
"icon": entry.icon, "isIdenticon": entry.isIdenticon,
"onlineStatus": entry.onlineStatus});
userListView.model = sortedList;
}
}
@@ -54,7 +152,16 @@ Item {
}
}
/*!
\qmlsignal
This signal is emitted when a new tag is created.
*/
signal addMember(string memberId)
/*!
\qmlsignal
This signal is emitted when a tag is removed.
*/
signal removeMember(string memberId)
Rectangle {
@@ -63,7 +170,6 @@ Item {
height: 44
radius: 8
color: Theme.palette.baseColor2
RowLayout {
anchors.fill: parent
anchors.leftMargin: 16
@@ -77,8 +183,8 @@ Item {
}
ScrollView {
Layout.preferredWidth: (namesList.contentWidth > (parent.width - 142)) ?
(parent.width - 142) : namesList.contentWidth
Layout.preferredWidth: (namesList.contentWidth > (parent.width - 177)) ?
(parent.width - 177) : namesList.contentWidth
implicitHeight: 30
Layout.alignment: Qt.AlignVCenter
visible: (namesList.count > 0)
@@ -92,9 +198,13 @@ Item {
model: namesModel
orientation: ListView.Horizontal
spacing: 8
onCountChanged: {
contentX = contentWidth;
function scrollToEnd() {
if (contentWidth > width) {
contentX = contentWidth;
}
}
onWidthChanged: { scrollToEnd(); }
onCountChanged: { scrollToEnd(); }
delegate: Rectangle {
id: nameDelegate
width: (nameText.contentWidth + 34)
@@ -129,30 +239,36 @@ Item {
}
}
TextEdit {
TextInput {
id: edit
verticalAlignment: Text.AlignVCenter
focus: true
color: Theme.palette.directColor1
clip: true
font.pixelSize: 15
wrapMode: TextEdit.NoWrap
font.family: Theme.palette.baseFont.name
Layout.fillWidth: true
Layout.preferredHeight: 44
verticalAlignment: Text.AlignVCenter
visible: (namesModel.count < 5)
enabled: visible
focus: true
font.pixelSize: 15
font.family: Theme.palette.baseFont.name
color: Theme.palette.directColor1
Keys.onPressed: {
if ((event.key === Qt.Key_Backspace || event.key === Qt.Key_Escape)
&& getText(cursorPosition, (cursorPosition-1)) === "") {
&& getText(cursorPosition, (cursorPosition-1)) === ""
&& (namesList.count-1) >= 0) {
removeMember(namesModel.get(namesList.count-1).publicId);
namesModel.remove((namesList.count-1), 1);
}
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (sortedList.count > 0)) {
root.insertTag(sortedList.get(userListView.currentIndex).name, sortedList.get(userListView.currentIndex).publicId);
}
}
Keys.onUpPressed: { userListView.decrementCurrentIndex(); }
Keys.onDownPressed: { userListView.incrementCurrentIndex(); }
}
StatusBaseText {
id: warningTextLabel
visible: (namesModel.count === root.nameCountLimit)
Layout.preferredWidth: 120
Layout.preferredWidth: visible ? 120 : 0
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
font.pixelSize: 10
color: Theme.palette.dangerColor1
@@ -167,8 +283,9 @@ Item {
anchors.left: parent.left
anchors.leftMargin: 8
anchors.top: tagSelectorRect.bottom
anchors.topMargin: 32
visible: (namesModel.count === 0)
anchors.topMargin: visible ? 32 : 0
height: visible ? contentHeight : 0
visible: (root.sortedList.count === 0)
color: Theme.palette.baseColor1
text: root.listLabel
}
@@ -178,12 +295,13 @@ Item {
width: 360
anchors {
top: (root.sortedList.count > 0) ? tagSelectorRect.bottom : contactsLabel.bottom
topMargin: 8//Style.current.padding
topMargin: 8//Style.current.halfPadding
bottom: parent.bottom
bottomMargin: 20//Style.current.bigPadding
bottomMargin: 16//Style.current.padding
}
visible: ((root.namesModel.count === 0) || (root.sortedList.count > 0))
x: ((root.namesModel.count > 0) && ((edit.x + 8) <= (root.width - suggestionsContainer.width)))
clip: true
visible: ((edit.text === "") || (root.sortedList.count > 0))
x: ((root.namesModel.count > 0) && (root.sortedList.count > 0) && ((edit.x + 8) <= (root.width - suggestionsContainer.width)))
? (edit.x + 8) : 0
background: Rectangle {
id: bgRect
@@ -215,12 +333,22 @@ Item {
policy: ScrollBar.AsNeeded
}
boundsBehavior: Flickable.StopAtBounds
onCountChanged: {
userListView.currentIndex = 0;
}
delegate: Item {
id: wrapper
anchors.right: parent.right
anchors.left: parent.left
height: 64
property bool hovered: false
height: visible ? 64 : 0
visible: {
for (let i = 0; i < namesModel.count; i++) {
if (namesModel.get(i).publicId === model.publicId) {
return false
}
}
return true
}
Rectangle {
id: rectangle
anchors.fill: parent
@@ -228,7 +356,7 @@ Item {
anchors.leftMargin: 8
radius: 8
visible: (root.sortedList.count > 0)
color: (wrapper.hovered) ? Theme.palette.baseColor2 : "transparent"
color: (userListView.currentIndex === index) ? Theme.palette.baseColor2 : "transparent"
}
StatusSmartIdenticon {
@@ -270,10 +398,7 @@ Item {
anchors.fill: parent
hoverEnabled: true
onEntered: {
wrapper.hovered = true;
}
onExited: {
wrapper.hovered = false;
userListView.currentIndex = index;
}
onClicked: {
root.insertTag(model.name, model.publicId);
+124 -8
View File
@@ -7,30 +7,114 @@ import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
/*!
\qmltype StatusToastMessage
\inherits Control
\inqmlmodule StatusQ.Components
\since StatusQ.Components 0.1
\brief Displays a toast message in the UI. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-controls2-control.html}{Control}.
The \c StatusToastMessage displays a toast message in the UI either standalone or as part
of a stack of toast messages.
For example:
\qml
StatusToastMessage {
primaryText: qsTr("Collectible is being minted...")
secondaryText: qsTr("View on Etherscan")
loading: true
type: 0
linkUrl: "http://google.com"
duration: 5000
}
\endqml
\image status_toast_message.png
For a list of components available see StatusQ.
*/
Control {
id: root
width: 343
height: !!secondaryText ? 68 : 48
anchors.right: parent.right
property bool open: false
/*!
\qmlproperty bool StatusToastMessage::open
This property represents all steps and their descriptions as provided by the user.
*/
property bool open: true
/*!
\qmlproperty string StatusToastMessage::primaryText
This property represents the title text of the ToastMessage.
*/
property string primaryText: ""
/*!
\qmlproperty string StatusToastMessage::secondaryText
This property represents the subtitle text of the ToastMessage.
*/
property string secondaryText: ""
/*!
\qmlproperty bool StatusToastMessage::loading
This property represents activates/deactivates the loading indicator of the ToastMessage.
*/
property bool loading: false
property string iconName: ""
/*!
\qmlproperty string StatusToastMessage::linkUrl
This property represents all steps and their descriptions as provided by the user.
*/
property string linkUrl: ""
/*!
\qmlproperty int StatusToastMessage::duration
This property represents duration in milliseconds, for how long a toast will be visible. If 0 is set for
duration, that means a toast won't dissapear without user's interaction (click on close).
*/
property int duration: 0
/*!
\qmlproperty StatusIconSettings StatusToastMessage::icon
This property holds a set of settings for the icon of the ToastMessage.
*/
property StatusIconSettings icon: StatusIconSettings {
width: 23
height: 23
}
/*!
\qmlproperty int StatusToastMessage::type
This property holds the type of the ToastMessage. Values are:
\list
\li Default
\li Success
\endlist
*/
property int type: StatusToastMessage.Type.Default
enum Type {
Default,
Success
}
/*!
\qmlmethod
This function is used to open the ToastMessage setting all its properties.
Examples of usage:
\qml
StatusToastMessage {
id: toastMessage
}
MouseArea {
anchors.fill: parent
onClicked: {
toastMessage.open(qsTr("Verification Request Sent"), "", "checkmark-circle", 1, false,"");
}
}
\endqml
*/
function open(title, subTitle, iconName, type, loading, url) {
root.primaryText = title;
root.secondaryText = subTitle;
@@ -41,12 +125,36 @@ Control {
root.open = true;
}
/*!
\qmlsignal
This signal is emitted when the ToastMessage is closed (after animation).
*/
signal close()
/*!
\qmlsignal
This signal is emitted when the ToastMessage contains a url and this url
is clicked by the user.
*/
signal linkActivated(var link)
QtObject {
id: d
readonly property string openedState: "opened"
readonly property string closedState: "closed"
}
Timer {
interval: root.duration
running: root.duration > 0
onTriggered: {
root.open = false;
}
}
states: [
State {
name: "opened"
name: d.openedState
when: root.open
PropertyChanges {
target: root
@@ -55,16 +163,13 @@ Control {
}
},
State {
name: "closed"
name: d.closedState
when: !root.open
PropertyChanges {
target: root
anchors.rightMargin: -width
opacity: 0.0
}
StateChangeScript {
script: { root.close(); }
}
}
]
@@ -72,6 +177,12 @@ Control {
Transition {
to: "*"
NumberAnimation { properties: "anchors.rightMargin,opacity"; duration: 400 }
onRunningChanged: {
if(!running && state == d.closedState) {
root.close();
}
}
}
]
@@ -114,9 +225,14 @@ Control {
radius: (root.width/2)
color: (root.type === StatusToastMessage.Type.Success) ?
Theme.palette.successColor2 : Theme.palette.primaryColor3
visible: loader.sourceComponent != undefined
Loader {
id: loader
anchors.centerIn: parent
sourceComponent: root.loading ? loadingInd : statusIcon
sourceComponent: root.loading ? loadingInd :
root.icon.name != ""? statusIcon :
undefined
Component {
id: loadingInd
StatusLoadingIndicator {
+33 -2
View File
@@ -6,10 +6,42 @@ import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
/*!
\qmltype StatusWizardStepper
\inherits Item
\inqmlmodule StatusQ.Components
\since StatusQ.Components 0.1
\brief Displays a step sequence provided by the stepsModel. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
The \c StatusWizardStepper steps and their descriptions as provided by the stepsModel.
For example:
\qml
StatusWizardStepper {
id: wizardStepper
stepsModel: ListModel {
ListElement {description: qsTr("Send Request"); loadingTime: 0; stepCompleted: false}
ListElement {description: qsTr("Receive Response"); loadingTime: 0; stepCompleted: false}
ListElement {description: qsTr("Confirm Identity"); loadingTime: 0; stepCompleted: false}
}
}
\endqml
\image status_wizard_stepper.png
For a list of components available see StatusQ.
*/
Item {
property int maxDuration: 2000
id: wizardWrapper
width: parent.width
height: 56
/*!
\qmlproperty ListModel StatusWizardStepper::stepsModel
This property represents all steps and their descriptions as provided by the user.
*/
property ListModel stepsModel: ListModel { }
ListView {
@@ -35,8 +67,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -12
from: 0
//TODO what is to: here?
to: 2000
to: wizardWrapper.maxDuration
value: loadingTime
backgroundColor: "transparent"
backgroundBorderColor: Theme.palette.primaryColor1
+2
View File
@@ -24,6 +24,7 @@ StatusRoundedImage 0.1 StatusRoundedImage.qml
StatusMacWindowButtons 0.1 StatusMacWindowButtons.qml
StatusListItemBadge 0.1 StatusListItemBadge.qml
StatusListItemTag 0.1 StatusListItemTag.qml
StatusListPicker 0.1 StatusListPicker.qml
StatusExpandableItem 0.1 StatusExpandableItem.qml
StatusSmartIdenticon 0.1 StatusSmartIdenticon.qml
StatusMessage 0.1 StatusMessage.qml
@@ -31,3 +32,4 @@ StatusMessageDetails 0.1 StatusMessageDetails.qml
StatusTagSelector 0.1 StatusTagSelector.qml
StatusToastMessage 0.1 StatusToastMessage.qml
StatusWizardStepper 0.1 StatusWizardStepper.qml
StatusImageCropPanel 0.1 StatusImageCropPanel.qml
@@ -55,7 +55,7 @@ Item {
return
}
if (selectedAccount.color) {
selectedIconImg.color = Utils.getThemeAccountColor(selectedAccount.color, Theme.palette.accountColors) || Theme.palette.accountColors[0]
selectedIconImg.color = Utils.getThemeAccountColor(selectedAccount.color, Theme.palette.userCustomizationColors) || Theme.palette.userCustomizationColors[0]
}
if (selectedAccount.name) {
selectedTextField.text = selectedAccount.name
@@ -208,7 +208,7 @@ Item {
width: 20
height: 20
icon: "filled-account"
color: Utils.getThemeAccountColor(model.color, Theme.palette.accountColors) || Theme.palette.accountColors[0]
color: Utils.getThemeAccountColor(model.color, Theme.palette.userCustomizationColors) || Theme.palette.userCustomizationColors[0]
}
Column {
+51 -1
View File
@@ -3,13 +3,61 @@ import QtQuick 2.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
/*!
\qmltype StatusBanner
\inherits Column
\inqmlmodule StatusQ.Controls
\since StatusQ.Controls 0.1
\brief It displays a banner with a custom text, size and type. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-column.html}{Column}.
The \c StatusBanner displays a banner with a custom text, size and type (Info, Danger, Success or Warning).
Example of how the control looks like:
\image status_banner.png
Example of how to use it:
\qml
StatusBanner {
width: parent.width
visible: popup.userIsBlocked
type: StatusBanner.Type.Danger
statusText: qsTr("Blocked")
}
\endqml
For a list of components available see StatusQ.
*/
Column {
id: statusBanner
width: parent.width
/*!
\qmlproperty string StatusBanner::statusText
This property holds the text the banner will display.
*/
property string statusText
/*!
\qmlproperty string StatusBanner::type
This property holds type of banner. Possible values are:
\qml
enum Type {
Info, // 0
Danger, // 1
Success, // 2
Warning // 3
}
\endqml
*/
property int type: StatusBanner.Type.Info
/*!
\qmlproperty string StatusBanner::textPixels
This property holds the pixels size of the text inside the banner.
*/
property int textPixels: 15
/*!
\qmlproperty string StatusBanner::statusBannerHeight
This property holds the height of the banner rectangle.
*/
property int statusBannerHeight: 38
// "private" properties
@@ -27,6 +75,8 @@ Column {
Warning // 3
}
width: parent.width
// Component definition
Rectangle {
id: topDiv
+14 -15
View File
@@ -40,11 +40,9 @@ Rectangle {
return 5
case StatusBaseButton.Size.Small:
return 10
break;
case StatusBaseButton.Size.Large:
default:
return 11
break;
}
}
property real defaultBottomPadding: {
@@ -53,11 +51,9 @@ Rectangle {
return 5
case StatusBaseButton.Size.Small:
return 10
break;
case StatusBaseButton.Size.Large:
default:
return 11
break;
}
}
@@ -109,6 +105,17 @@ Rectangle {
hoverEnabled: !loading
enabled: !loading
Loader {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
active: loading
sourceComponent: StatusLoadingIndicator {
color: statusBaseButton.enabled ? textColor
: disabledTextColor
} // Indicator
} // Loader
Row {
id: layout
anchors.left: parent.left
@@ -121,28 +128,20 @@ Rectangle {
height: statusBaseButton.icon.height
icon: statusBaseButton.icon.name
anchors.verticalCenter: parent.verticalCenter
visible: !loading && statusBaseButton.icon.name !== ""
opacity: !loading && statusBaseButton.icon.name !== ""
visible: statusBaseButton.icon.name !== ""
color: statusBaseButton.enabled ? textColor
: disabledTextColor
} // Icon
StatusBaseText {
id: label
visible: !loading
opacity: !loading
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: size === StatusBaseButton.Size.Large ? 15 : 13 // by design
color: statusBaseButton.enabled ? textColor
: disabledTextColor
} // Text
Loader {
anchors.verticalCenter: parent.verticalCenter
active: loading
sourceComponent: StatusLoadingIndicator {
color: statusBaseButton.enabled ? textColor
: disabledTextColor
} // Indicator
} // Loader
} // Ro
+244 -18
View File
@@ -8,52 +8,224 @@ import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
/*!
\qmltype StatusBaseInput
\inherits Item
\inqmlmodule StatusQ.Controls
\since StatusQ.Controls 0.1
\brief Displays a customized TextInput component.
Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
The \c StatusBaseInput displays an enhanced TextInput for users to type text.
For example:
\qml
StatusBaseInput {
implicitWidth: parent.width
implicitHeight: 36
topPadding: 8
bottomPadding: 8
clearable: true
placeholderText: qsTr("Search")
icon.name: "search"
}
\endqml
\image status_base_input.png
For a list of components available see StatusQ.
*/
Item {
id: root
/*!
\qmlproperty bool StatusBaseInput::multiline
This property indicates whether the StatusBaseInput allows multiline text. Default value is false.
*/
property bool multiline: false
/*!
\qmlproperty bool StatusBaseInput::acceptReturn
This property indicates whether the StatusBaseInput allows pressing Enter or Return.
This is used in case multiline is false and we still want to interact using Enter or Return. Default value is false.
*/
property bool acceptReturn: false
/*!
\qmlproperty bool StatusBaseInput::clearable
This property indicates whether the StatusBaseInput allows clearing all text. Default value is false.
*/
property bool clearable: false
/*!
\qmlproperty alias StatusBaseInput::inputMethodHints
This property holds a reference to the TextEdit component's inputMethodHints property.
*/
property alias inputMethodHints: edit.inputMethodHints
/*!
\qmlproperty alias StatusBaseInput::selectedText
This property holds a reference to the TextEdit component's selectedText property.
*/
property alias selectedText: edit.selectedText
/*!
\qmlproperty alias StatusBaseInput::selectedTextColor
This property holds a reference to the TextEdit component's selectedTextColor property.
*/
property alias selectedTextColor: edit.selectedTextColor
/*!
\qmlproperty alias StatusBaseInput::selectionStart
This property holds a reference to the TextEdit component's selectionStart property.
*/
property alias selectionStart: edit.selectionStart
/*!
\qmlproperty alias StatusBaseInput::selectionEnd
This property holds a reference to the TextEdit component's selectionEnd property.
*/
property alias selectionEnd: edit.selectionEnd
/*!
\qmlproperty alias StatusBaseInput::cursorPosition
This property holds a reference to the TextEdit component's cursorPosition property.
*/
property alias cursorPosition: edit.cursorPosition
/*!
\qmlproperty alias StatusBaseInput::edit
This property holds a reference to the TextEdit component.
*/
property alias edit: edit
/*!
\qmlproperty alias StatusBaseInput::text
This property holds a reference to the TextEdit component's text property.
*/
property alias text: edit.text
/*!
\qmlproperty alias StatusBaseInput::background
This property holds a reference to the background component.
*/
property alias background: background
/*!
\qmlproperty alias StatusBaseInput::color
This property holds a reference to the TextEdit component's color property.
*/
property alias color: edit.color
/*!
\qmlproperty alias StatusBaseInput::font
This property holds a reference to the TextEdit component's font property.
*/
property alias font: edit.font
/*!
\qmlproperty alias StatusBaseInput::focussed
This property holds a reference to the TextEdit component's activeFocus property.
*/
property alias focussed: edit.activeFocus
property alias verticalAlignmet: edit.verticalAlignment
/*!
\qmlproperty alias StatusBaseInput::verticalAlignment
This property holds a reference to the TextEdit component's verticalAlignment property.
*/
property alias verticalAlignment: edit.verticalAlignment
/*!
\qmlproperty alias StatusBaseInput::horizontalAlignment
This property holds a reference to the TextEdit component's horizontalAlignment property.
*/
property alias horizontalAlignment: edit.horizontalAlignment
/*!
\qmlproperty alias StatusBaseInput::placeholderText
This property holds a reference to the placeholder component's placeholderText property.
*/
property alias placeholderText: placeholder.text
/*!
\qmlproperty alias StatusBaseInput::placeholderTextColor
This property holds a reference to the placeholder component's placeholderTextColor property.
*/
property alias placeholderTextColor: placeholder.color
/*!
\qmlproperty alias StatusBaseInput::placeholderFont
This property holds a reference to the placeholder component's font property.
*/
property alias placeholderFont: placeholder.font
property real leftPadding: 16
/*!
\qmlproperty real StatusBaseInput::leftPadding
This property sets the leftComponentLoader's left padding.
*/
property real leftPadding: leftComponentLoader.item ? 8 : 16
/*!
\qmlproperty real StatusBaseInput::rightPadding
This property sets the right padding.
*/
property real rightPadding: 16
/*!
\qmlproperty real StatusBaseInput::topPadding
This property sets the top padding.
*/
property real topPadding: 12
/*!
\qmlproperty alias StatusBaseInput::bottomPadding
This property sets the bottom padding.
*/
property real bottomPadding: 12
/*!
\qmlproperty var StatusBaseInput::tabNavItem
This property sets the tab key navigation item.
*/
property var tabNavItem: null
/*!
\qmlproperty real StatusBaseInput::minimumHeight
This property sets the minimum height.
*/
property real minimumHeight: 0
/*!
\qmlproperty alias StatusBaseInput::maximumHeight
This property sets the maximum height.
*/
property real maximumHeight: 0
/*!
\qmlproperty int StatusBaseInput::maximumLength
This property sets the text's maximum length.
*/
property int maximumLength: 0
/*!
\qmlproperty bool StatusBaseInput::valid
This property sets the valid state. Default value is true.
*/
property bool valid: true
/*!
\qmlproperty bool StatusBaseInput::pristine
This property sets the pristine. Default value is true.
*/
property bool pristine: true
/*!
\qmlproperty bool StatusBaseInput::dirty
This property sets the dirty property. Default value is false.
*/
property bool dirty: false
/*!
\qmlproperty bool StatusBaseInput::pending
This property holds the maximum height of the component. Default value is false.
*/
property bool pending: false
/*!
\qmlproperty bool StatusBaseInput::leftIcon
This property sets the visibility of the left icon. Default value is true.
*/
property bool leftIcon: true
/*!
\qmlproperty bool StatusBaseInput::isIconSelectable
This property indicates whether icon is selectable. Default value is false.
*/
property bool isIconSelectable: false
/*!
\qmlproperty bool StatusBaseInput::showBackground
This property sets the visibility of the background. Default value is true.
*/
property bool showBackground: true
/*!
\qmlproperty StatusIconSettings StatusBaseInput::icon
This property holds a set of settings for the icon of the StatusBaseInput.
*/
property StatusIconSettings icon: StatusIconSettings {
width: 24
height: 24
@@ -68,10 +240,37 @@ Item {
}
}
/*!
\qmlproperty string StatusBaseInput::letterIconName
This property holds the letter that should be displayed in the letter identicon component.
*/
property string letterIconName: ""
/*!
\qmlproperty Component StatusBaseInput::leftComponent
This property sets the left component to be displayed inside the input.
*/
property Component leftComponent
/*!
\qmlproperty Component StatusBaseInput::selectedText
This property sets the right component to be displayed inside the input.
*/
property Component rightComponent
/*!
\qmlsignal
This signal is emitted when the icon is clicked.
*/
signal iconClicked
/*!
\qmlsignal
This signal is emitted when a hard key is pressed passing as parameter the keyboard event.
*/
signal keyPressed(var event)
/*!
\qmlsignal
This signal is emitted when the text edit is clicked.
*/
signal editClicked()
implicitWidth: 448
implicitHeight: multiline ? Math.min(Math.max(
@@ -109,15 +308,17 @@ Item {
MouseArea {
id: sensor
enabled: !edit.activeFocus
hoverEnabled: true
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: {
if (edit.activeFocus)
return;
edit.forceActiveFocus()
root.editClicked()
}
RowLayout {
spacing: 10
anchors {
fill: parent
leftMargin: root.leftPadding
@@ -127,10 +328,11 @@ Item {
clip: true
Loader {
id: leftComponentLoader
sourceComponent: {
if (root.leftComponent) return root.leftComponent
if (!root.leftIcon) return undefined
if (root.icon.emoji) return identiconComponent
if (root.icon.emoji || root.icon.isLetterIdenticon) return identiconComponent
if (root.icon.name) return isIconSelectable ? identiconComponent : iconComponent
return undefined
}
@@ -172,7 +374,7 @@ Item {
width: flick.width
height: flick.height
verticalAlignment: Text.AlignVCenter
verticalAlignment: TextEdit.AlignVCenter
selectByMouse: true
selectionColor: Theme.palette.primaryColor2
selectedTextColor: color
@@ -182,14 +384,23 @@ Item {
color: Theme.palette.directColor1
wrapMode: root.multiline ? Text.WrapAtWordBoundaryOrAnywhere : TextEdit.NoWrap
Keys.onReturnPressed: event.accepted = !multiline
Keys.onEnterPressed: event.accepted = !multiline
Keys.onReturnPressed: event.accepted = !multiline && !acceptReturn
Keys.onEnterPressed: event.accepted = !multiline && !acceptReturn
Keys.forwardTo: [root]
Keys.onPressed: edit.keyEvent = event.key
KeyNavigation.priority: !!root.tabNavItem ? KeyNavigation.BeforeItem : KeyNavigation.AfterItem
KeyNavigation.tab: root.tabNavItem
Keys.onPressed: {
edit.keyEvent = event.key
root.keyPressed(event);
}
onCursorRectangleChanged: flick.ensureVisible(cursorRectangle)
onActiveFocusChanged: if (root.pristine) root.pristine = false
onTextChanged: {
if (previousText === text) {
// Not sure why, but the textChanged event was triggered even if it didn't really
return
}
root.dirty = true
if (root.maximumLength > 0) {
if (text.length > root.maximumLength) {
@@ -205,13 +416,26 @@ Item {
}
}
cursorDelegate: Rectangle {
color: Theme.palette.primaryColor1
implicitWidth: 2
implicitHeight: 22
radius: 1
visible: edit.cursorVisible
SequentialAnimation on visible {
loops: Animation.Infinite
running: edit.cursorVisible
PropertyAnimation { to: false; duration: 600; }
PropertyAnimation { to: true; duration: 600; }
}
}
StatusBaseText {
id: placeholder
visible: (edit.text.length === 0)
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: root.rightPadding
anchors.verticalCenter: parent.verticalCenter
anchors.fill: parent
verticalAlignment: parent.verticalAlignment
font.pixelSize: 15
elide: StatusBaseText.ElideRight
font.family: Theme.palette.baseFont.name
@@ -260,13 +484,15 @@ Item {
StatusSmartIdenticon {
id: identicon
icon.width: !root.icon.emoji ? 20 : 30
icon.height: !root.icon.emoji ? 20 : 30
icon.width: 24
icon.height: 24
icon.background: root.icon.background
icon.color: root.icon.color
icon.letterSize: root.icon.letterSize
icon.emoji: root.icon.emoji
icon.name: !root.icon.emoji ? root.icon.name : ""
icon.isLetterIdenticon: root.icon.isLetterIdenticon
name: root.letterIconName
MouseArea {
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
@@ -25,8 +25,10 @@ Rectangle {
property StatusIconSettings icon: StatusIconSettings {
width: 36
height: 36
letterSize: 20
charactersLen: 2
}
property alias ringSettings: identicon.ringSettings
property int type: StatusChatInfoButton.Type.PublicChat
property alias tooltip: statusToolTip
property alias sensor: sensor
+2 -1
View File
@@ -11,6 +11,8 @@ TabButton {
property string name: ""
property bool highlighted: false
property alias identicon: identicon
implicitWidth: 40
implicitHeight: 40
@@ -32,7 +34,6 @@ TabButton {
icon.name: statusIconTabButton.icon.name
icon.color: (statusIconTabButton.hovered || highlighted || statusIconTabButton.checked) ? Theme.palette.primaryColor1 : statusIconTabButton.icon.color
icon.isLetterIdenticon: statusIconTabButton.name !== "" && !statusIconTabButton.icon.source.toString()
icon.letterSize: 15
name: statusIconTabButton.name
}
}
@@ -79,6 +79,9 @@ Item {
}
function getSegmentsCount() {
if (typeof settings.ringSpecModel.rowCount !== "undefined") {
return settings.ringSpecModel.rowCount()
}
if (typeof settings.ringSpecModel.count !== "undefined") {
return settings.ringSpecModel.count
}
@@ -86,6 +89,9 @@ Item {
}
function getSegment(i) {
if (typeof settings.ringSpecModel.rowCount !== "undefined") {
return abstactItemModelWrapper.itemAt(i)
}
if (typeof settings.ringSpecModel.count !== "undefined") {
return settings.ringSpecModel.get(i)
}
@@ -101,6 +107,14 @@ Item {
return Math.max(1, units)
}
Repeater {
id: abstactItemModelWrapper
model: typeof settings.ringSpecModel.rowCount !== "undefined" ? settings.ringSpecModel : null
delegate: Item {
readonly property int segmentLength: model.segmentLength
readonly property int colorId: model.colorId
}
}
onPaint: {
const context = getContext("2d")
@@ -127,6 +141,11 @@ Item {
}
}
}
Connections {
target: root.settings
onRingSpecModelChanged: requestPaint()
}
}
}
}
+359
View File
@@ -0,0 +1,359 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtGraphicalEffects 1.1
import StatusQ.Core.Utils 0.1
import StatusQ.Core.Theme 0.1
/*!
\qmltype StatusImageCrop
\inherits Item
\inqmlmodule StatusQ.Controls
\since StatusQ.Controls 0.1
\brief Draw a crop-window onto an image. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
Sizing
User resizes the control as needed. Control adapts and centers the crop
window inside the available space
Performance
The \c StatusImageCrop uses Canvas to draw the overlay in multiple steps.
Drawing with canvas is inefficient therefore keep the usage to a minimum,
as in the required space
API
The setCropRect is the only way to set the content shown in the crop window.
/note "Fit window" used here reffer to optimum zoom to fit all the content
in the view window; produces window borders if AR of the image is different
than the window. This ia opposite to "Fill window" reffers to optimum zoom
to fill all the view; parts of the content won't be visible if AR of the
image is different than the AR of window
/note The math is based on pixels so no subpixel accuracy. Didn't find it necessary for the current needs
/note windowRect aspect ratio and cropRect aspect ratio are keept in sync
Example of two controls; top WindowStyle.Rounded, bottom WindowStyle.Rectangular
\image eg-StatusImageCrop.png
Example of how to use it:
\qml
StatusImageCrop {
anchors.fill: parent
width: 100
height: 100 * aspectRatio
source: "qrc:/demoapp/data/logo-test-image.png"
windowStyle: StatusImageCrop.WindowStyle.Rounded
}
\endqml
For a list of components available see StatusQ.
*/
Item {
id: root
implicitWidth: 400
implicitHeight: 400 / aspectRatio
/*!
\qmlproperty url StatusImageCrop::source
Path to the image to be cropped
*/
/*required*/ property alias source: mainImage.source
enum WindowStyle {
Rounded,
Rectangular
}
/*!
\qmlproperty WindowStyle StatusImageCrop::windowStyle
Crop window styles
\value Rounded ellipses bounded by cropRect rectangle
\value Rectangular with rounded corners specified by radius
*/
property int windowStyle: StatusImageCrop.WindowStyle.Rectangular
/*!
\qmlproperty int StatusImageCrop::radius
Valid only when windowStyle is Rounded
*/
property int radius: 8
/*!
\qmlproperty color StatusImageCrop::wallColor
Color used outside the crop-rect. Tells user which part won't be available after cropping
*/
property color wallColor: Theme.palette.black
/*!
\qmlproperty real StatusImageCrop::wallTransparency
Transparency outside the crop-rect 0.0 - 1.0 (inclusive). Tells user which part won't
be available after cropping
*/
property real wallTransparency: 0.7
/*!
\qmlproperty bool StatusImageCrop::allowZoomToFitAllContent
\c true to limit the content to fit at minimum zoom; that's it, the user will see the entire image
in the crop window, in case of aspect-ratio mismatch, allowing transparent/no-content borders in the crop window.
\c false to limit the content to fill at minimum zoom; that's it, the user will see only part of the image
in the crop window in case of aspect-ratio mismatch. This way, we don't allow transparent/no-content space
*/
property bool allowZoomToFitAllContent: false
/*!
\qmlproperty real StatusImageCrop::minZoomScale
Minimum allowed zoom. This is 1 if \c allowZoomToFitAllContent is true else depends on the
source aspect ratio.
*/
readonly property real minZoomScale: allowZoomToFitAllContent ? 1 : d.zoomToFill
/*!
\qmlproperty real StatusImageCrop::maxZoomScale
Don't allow to zoom in more than maxZoomScale factor
*/
property real maxZoomScale: 10
/*!
\qmlproperty rect StatusImageCrop::cropRect
The content shown in the crop window. Values are expected to be in image coordinates.
The crop-window aspect-ratio is adjusted to match the cropRect value.
The default value shows an 1.0 AR to Fit Window
e.g. to set the crop window to show and match image's AR use Qt.rect(0, 0, sourceSize.width, sourceSize.height)
*/
readonly property rect cropRect: d.cropRect
/*!
\qmlproperty rect StatusImageCrop::cropRect
The position and zis of the crop rectangle in item's space; without extra spacing
*/
readonly property alias cropWindow: d.cropWindow
/*!
\qmlproperty real StatusImageCrop::aspectRatio
*/
readonly property real aspectRatio: d.cropRect.height !== 0 ? d.cropRect.width/d.cropRect.height : 0
/*!
\qmlproperty real StatusImageCrop::zoomScale
*/
readonly property real zoomScale: d.currentZoom(sourceSize, Qt.size(d.cropRect.width, d.cropRect.height))
/*!
\qmlproperty size StatusImageCrop::sourceSize
Image size if one is set. StatusImageCrop::cropRect is relative to the rect with top-corner
(0,0) and StatusImageCrop::sourceSize
\sa StatusImageCrop::source
*/
readonly property size sourceSize: mainImage.sourceSize
/*!
\qmlproperty real StatusImageCrop::scrToImgScale
Screen crop window to \a cropRect ratio. Can be used to translate from
screen coordinates into image coordinates e.g. to adjust \a cropRect accordingly
\note windowRect aspect-ratio and d.cropRect aspect-ratio are the same
\sa StatusImageCrop::cropRect
*/
readonly property real scrToImgScale: windowRect.wW/d.cropRect.width
/*!
\qmlmethod StatusImageCrop::setCropRect(rect)
The only way to set StatusImageCrop::cropRect from outside
The new rect will be adjusted to account for the zoom [1, StatusImageCrop::maxZoomScale]
\note the source image must be set and \c sourceSize valid otherwise, an error is logged to \c console
\note If the new rect has a diferent area the crop window will adjust to the new AR
*/
function setCropRect(newRect /*rect*/) {
if(newRect.width === 0 || newRect.height === 0)
return
if(root.sourceSize.width === 0 || root.sourceSize.height === 0)
console.error("Wrong source size. Ensure source is set")
let n = newRect
const s = root.sourceSize
let nZoom = d.currentZoom(s, Qt.size(n.width, n.height))
if(nZoom > root.maxZoomScale) {
nZoom = root.maxZoomScale
n = root.getZoomRect(nZoom)
}
else if(nZoom < root.minZoomScale) {
nZoom = root.minZoomScale
n = root.getZoomRect(nZoom)
}
// Limit panning
if((n.width/n.height) < (s.width/s.height)) {
// Crop window narrower than source
if(n.x < 0)
n.x = 0
const upBoundY = n.height > s.height ? s.height - n.height : 0
if(n.y < upBoundY)
n.y = upBoundY
if((n.x + n.width) > s.width)
n.x = s.width - n.width
const loBoundY = n.height > s.height ? 0 : s.height - n.height
if(n.y > loBoundY)
n.y = loBoundY
}
else {
// Crop window wider than source
const leftBoundX = n.width > s.width ? s.width - n.width : 0
if(n.x < leftBoundX)
n.x = leftBoundX
if(n.y < 0)
n.y = 0
const rightBoundY = n.width > s.width ? 0 : s.width - n.width
if(n.x > rightBoundY)
n.x = rightBoundY
if((n.y + n.height) > s.height)
n.y = s.height - n.height
}
d.cropRect = n
}
function getZoomRect(scale /*real*/) {
const oldCenter = root.rectCenter(root.cropRect)
const inflatedRect = root.inflateRectBy(root.minimumCropRect(), 1/scale)
return root.recenterRect(inflatedRect, oldCenter);
}
/*!
\qmlmethod StatusImageCrop::inflateRectBy(rect target, real scale)
Inflates the curren \a target rectangle with the \a scale while keeping the center fixed
*/
function inflateRectBy(target /*rect*/, scale /*real*/) {
const inflatedWidth = target.width * scale
const inflatedHeight = target.height * scale
return Qt.rect(target.x - (inflatedWidth - target.width)/2, target.y - (inflatedHeight - target.height)/2, inflatedWidth, inflatedHeight)
}
/*!
\qmlmethod StatusImageCrop::rectCenter(rect target)
Returns the center point of the \a target rectangle as a Qt.point
*/
function rectCenter(target /*rect*/) /*Qt.point*/ {
return Qt.point(target.x + target.width/2, target.y + target.height/2)
}
/*!
\qmlmethod StatusImageCrop::recenterRect(rect target)
Move the \a target rectangle's center to a /a newCenter
*/
function recenterRect(target /*rect*/, newCenter/*point*/) {
return Qt.rect(newCenter.x - target.width/2 , newCenter.y - target.height/2, target.width, target.height)
}
//> cropRect for minimum zoom: 1.0
function minimumCropRect() {
const sourceAR = root.sourceSize.width/root.sourceSize.height
const widthBound = sourceAR > root.aspectRatio
const minCropSize = widthBound ? Qt.size(root.sourceSize.width, root.sourceSize.width/root.aspectRatio)
: Qt.size(root.sourceSize.height * root.aspectRatio, root.sourceSize.height)
let res = Qt.rect(widthBound ? 0 : -(root.sourceSize.width - minCropSize.width)/2, // x
widthBound ? -(root.sourceSize.height - minCropSize.height)/2 : 0, // y
minCropSize.width, minCropSize.height)
return res
}
QtObject {
id: d
property rect cropRect: fillContentInSquaredWindow(root.sourceSize)
onCropRectChanged: windowRect.requestPaint()
readonly property real zoomToFill: {
const rectangle = fillContentInSquaredWindow(root.sourceSize)
return d.currentZoom(root.sourceSize, Qt.size(rectangle.width, rectangle.height))
}
property rect cropWindow
// Probably called from render thread, run async
signal updateCropWindow(rect newRect)
onUpdateCropWindow: cropWindow = newRect
function fillContentInSquaredWindow(c /*size*/) {
if(c.width > c.height) {
const border = (c.width - c.height)/2
return Qt.rect(border, 0, c.height, c.height)
}
else {
const border = (c.height - c.width)/2
return Qt.rect(0, border, c.width, c.width)
}
}
//> 1.0 is the content represented by w fully inscribed in c
function currentZoom(c /*size*/, w /*size*/) {
const wScale = c.width/w.width
const hScale = c.height/w.height
return Math.max(wScale, hScale)
}
}
onWindowStyleChanged: windowRect.requestPaint()
onSourceSizeChanged: d.cropRect = d.fillContentInSquaredWindow(sourceSize)
Canvas {
id: windowRect
anchors.fill: parent
layer.enabled: true
property bool widthFit: (root.width / root.aspectRatio) <= root.height
// Window width
property real wW: widthFit ? root.width : root.height * root.aspectRatio
// Window height
property real wH: widthFit ? root.width / root.aspectRatio : root.height
onPaint: {
var ctx = getContext("2d")
ctx.save()
// TODO: observed drawing artefacts at the edge. Draw one pixel more for now until root cause is found
ctx.clearRect(0, 0, width + 1, height + 1)
// Fill all with wallColor in order to clip the window from it
ctx.fillStyle = Qt.rgba(root.wallColor.r, root.wallColor.g, root.wallColor.b, root.wallTransparency)
ctx.fillRect(0, 0, width + 1, height + 1)
// Cut opaque new pixels from background
ctx.globalCompositeOperation = "source-out"
// Draw the window
ctx.beginPath()
const cW = Qt.rect((width - wW)/2, (height - wH)/2, wW, wH)
if(root.windowStyle === StatusImageCrop.WindowStyle.Rounded)
ctx.ellipse(cW.x, cW.y, cW.width, cW.height)
else if(root.windowStyle === StatusImageCrop.WindowStyle.Rectangular)
ctx.roundedRect(cW.x, cW.y, cW.width, cW.height, root.radius, root.radius)
ctx.fill()
ctx.restore()
d.updateCropWindow(cW)
}
}
Image {
id: mainImage
fillMode: Image.PreserveAspectFit
z: windowRect.z - 1
// Transform to keep the center of the image window in x and y coordinates
transform: [
Translate {
readonly property real s: mainImage.scale
x: -(d.cropRect.x + d.cropRect.width/2)
y: -(d.cropRect.y + d.cropRect.height/2)
},
Scale {
xScale: root.scrToImgScale
yScale: root.scrToImgScale
}
]
// Align window center to window rect
x: windowRect.x + windowRect.width/2
y: windowRect.y + windowRect.height/2
}
}
+121 -22
View File
@@ -13,18 +13,15 @@ import StatusQ.Controls.Validators 0.1
\since StatusQ.Controls 0.1
\brief The StatusInput control provides a generic user text input
Example of how to use it:
\qml
StatusInput {
label: "Label"
charLimit: 30
errorMessage: "Input doesn't match validator"
errorMessage: qsTr("Input doesn't match validator")
input.clearable: true
input.placeholderText: "Placeholder text"
input.placeholderText: qsTr("Placeholder text")
}
\endqml
@@ -33,23 +30,48 @@ import StatusQ.Controls.Validators 0.1
Item {
id: root
implicitWidth: 480
height: (label.visible ?
label.anchors.topMargin +
label.height :
charLimitLabel.visible ?
charLimitLabel.anchors.topMargin +
charLimitLabel.height :
0) +
statusBaseInput.anchors.topMargin +
statusBaseInput.height +
(errorMessage.visible ?
errorMessage.anchors.topMargin +
errorMessage.height :
0) + 8
implicitHeight: (label.visible ?
label.anchors.topMargin +
label.height :
charLimitLabel.visible ?
charLimitLabel.anchors.topMargin +
charLimitLabel.height :
0) +
statusBaseInput.anchors.topMargin +
statusBaseInput.height +
(errorMessage.visible ?
errorMessage.anchors.topMargin +
errorMessage.height :
0) + 8
height: implicitHeight
width: implicitWidth
/*!
\qmlproperty alias StatusInput::input
This property holds a reference to the TextEdit component.
*/
property alias input: statusBaseInput
/*!
\qmlproperty string StatusInput::letterIconName
This property holds a reference to the StatusBaseInput's letterIconName property.
*/
property alias letterIconName: statusBaseInput.letterIconName
/*!
\qmlproperty alias StatusInput::valid
This property holds a reference to the TextEdit's valid property.
*/
property alias valid: statusBaseInput.valid
/*!
\qmlproperty alias StatusInput::pending
This property holds a reference to the TextEdit's pending property.
*/
property alias pending: statusBaseInput.pending
/*!
\qmlproperty alias StatusInput::text
This property holds a reference to the TextEdit's text property.
*/
property alias text: statusBaseInput.text
/*!
@@ -68,16 +90,48 @@ Item {
\endqml
*/
property alias errorMessageCmp: errorMessage
/*!
\qmlproperty string StatusInput::label
This property sets the label text.
*/
property string label: ""
/*!
\qmlproperty string StatusInput::secondaryLabel
This property sets the secondary label text.
*/
property string secondaryLabel: ""
/*!
\qmlproperty int StatusInput::charLimit
This property sets the character limit of the text input.
*/
property int charLimit: 0
/*!
\qmlproperty string StatusInput::errorMessage
This property sets the error message text.
*/
property string errorMessage: ""
/*!
\qmlproperty real StatusBaseInput::leftPadding
This property sets the leftComponentLoader's left padding.
*/
property real leftPadding: 16
/*!
\qmlproperty real StatusBaseInput::rightPadding
This property sets the right padding.
*/
property real rightPadding: 16
/*!
\qmlproperty list StatusBaseInput::validators
This property sets the list of validators to be considered.
*/
property list<StatusValidator> validators
/*!
\qmlproperty list StatusBaseInput::validators
This property sets the list of async validators to be considered.
*/
property list<StatusAsyncValidator> asyncValidators
/*!
\qmlproperty int (enumeration ValidationMode)
\qmlproperty int StatusInput::validationMode
This property describes the validation mode
Note that this property should be always strictly compared with one of ValidationMode's value
@@ -99,11 +153,32 @@ Item {
\endqml
*/
property int validationMode: StatusInput.ValidationMode.OnlyWhenDirty
/*!
\qmlproperty string StatusBaseInput::validatedValue
This property sets the validated value text.
*/
property string validatedValue
/*!
\qmlproperty var StatusBaseInput::pendingValidators
This property sets the pending validators to be considered.
*/
property var pendingValidators: []
/*!
\qmlsignal
This signal is emitted when the icon is clicked.
*/
signal iconClicked()
/*!
\qmlsignal
This signal is emitted when a hard key is pressed passing as parameter the keyboard event.
*/
signal keyPressed(var event)
/*!
\qmlsignal
This signal is emitted when the text edit is clicked.
*/
signal editClicked()
/*!
\qmltype ValidationMode
@@ -128,9 +203,20 @@ Item {
IgnoreInvalidInput // ignore the new content if it doesn't match
}
/*!
\qmlproperty var StatusBaseInput::errors
This property holds the validation errors.
*/
property var errors: ({})
/*!
\qmlproperty var StatusBaseInput::errors
This property holds the validation async errors.
*/
property var asyncErrors: ({})
/*!
\qmlmethod
This function resets the text input validation and text.
*/
function reset() {
statusBaseInput.valid = true
statusBaseInput.pristine = true
@@ -139,6 +225,10 @@ Item {
}
property string _previousText: text
/*!
\qmlmethod
This function validates the text input's text.
*/
function validate() {
if (!statusBaseInput.dirty && validationMode === StatusInput.ValidationMode.OnlyWhenDirty) {
@@ -203,7 +293,10 @@ Item {
root.validatedValue = root.text
}
}
/*!
\qmlmethod
This function updates the text input async validation.
*/
function updateAsyncValidity(validatorName, value, result) {
if (!asyncErrors) {
asyncErrors = {}
@@ -287,6 +380,12 @@ Item {
Keys.forwardTo: [root]
onIconClicked: root.iconClicked()
onKeyPressed: {
root.keyPressed(event);
}
onEditChanged: {
root.editClicked();
}
}
StatusBaseText {
+189
View File
@@ -0,0 +1,189 @@
import QtQuick 2.0
import QtQuick.Controls 2.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
/*!
\qmltype StatusItemPicker
\inherits Item
\inqmlmodule StatusQ.Controls
\since StatusQ.Controls 0.1
\brief It presents a selectable item to the user. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-rectangle.html}{Rectangle}.
The \c StatusItemPicker is populated with the given properties data.
StatusItemPicker can be made as a RadioButton or CheckBox selectable item.
Example of how the component looks like:
\image status_item_picker.png
Example of how to use it:
\qml
StatusItemPicker {
width: 300
height: 40
imageSource: model.imageSource
name: model.name
shortName: model.shortName
selectorType: root.multiSelection ? StatusItemPicker.SelectorType.CheckBox : StatusItemPicker.SelectorType.RadioButton
selected: model.selected
radioGroup: content.radioGroup
onCheckedChanged: { // Some updates }
}
\endqml
For a list of components available see StatusQ.
*/
Rectangle {
id: root
/*!
\qmlproperty string StatusItemPicker::image
This property holds the image settings information.
*/
property StatusImageSettings image
/*!
\qmlproperty string StatusItemPicker::name
This property holds the main text or name to be displayed.
*/
property string name
/*!
\qmlproperty string StatusItemPicker::shortName
This property holds the secondary text or short name to be displayed.
*/
property string shortName
/*!
\qmlproperty string StatusItemPicker::selectorType
This property holds the selector type. Possible options are:
\qml
enum SelectorType {
RadioButton,
CheckBox
}
\endqml
By default, RadioButton.
*/
property int selectorType: StatusItemPicker.SelectorType.RadioButton
/*!
\qmlproperty string StatusItemPicker::selected
This property holds if the item is selected.
*/
property bool selected
/*!
\qmlproperty string StatusItemPicker::radioGroup
This property holds the button group object the radiobutton belongs to.
*/
property ButtonGroup radioGroup
/*!
\qmlsignal StatusItemPicker::checkedChanged(bool checked)
This signal is emitted when the item is selected by pressing the radiobutton or the checkbox.
*/
signal checkedChanged(bool checked)
enum SelectorType {
RadioButton,
CheckBox
}
QtObject {
id: d
property int minShortNameWidth: 50
function availableTextWidth() {
return root.width - imageItem.width - row.spacing - shortNameItem.anchors.rightMargin - selector.width - selector.anchors.rightMargin - 24/*Margin between both texts*/
}
}
Row {
id: row
anchors.left: parent.left
anchors.leftMargin: 18
anchors.verticalCenter: parent.verticalCenter
spacing: 8
StatusIcon {
id: imageItem
anchors.verticalCenter: parent.verticalCenter
source: root.image.source ? root.image.source : ""
width: root.image.width
height: root.image.height
visible: root.image.source !== undefined
}
StatusBaseText {
id: nameItem
anchors.verticalCenter: parent.verticalCenter
width: dummyNameItem.width > d.availableTextWidth() - d.minShortNameWidth ?
d.availableTextWidth() - d.minShortNameWidth :
dummyNameItem.width
text: root.name
color: Theme.palette.directColor1
font.pixelSize: 15
clip: true
elide: Text.ElideRight
}
// Dummy object just to exactly know the width needed by `name` and dynamically set a limit for nameItem and shortNameItem components
StatusBaseText {
id: dummyNameItem
visible: false
text: root.name
font.pixelSize: 15
}
}
StatusBaseText {
id: shortNameItem
width: d.availableTextWidth() - nameItem.width < d.minShortNameWidth ?
d.minShortNameWidth :
d.availableTextWidth() - nameItem.width
anchors.right: selector.left
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
text: root.shortName
color: Theme.palette.baseColor1
font.pixelSize: 15
clip: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignRight
}
// 2 different options: Or with radio buttons or with checkboxes:
Loader {
id: selector
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 18
sourceComponent: root.selectorType === StatusItemPicker.SelectorType.RadioButton ? radioBtn : checkbox
}
Component {
id: radioBtn
StatusRadioButton {
ButtonGroup.group: root.radioGroup
checked: root.selected
onCheckedChanged: {
root.selected = checked
root.checkedChanged(checked)
}
}
}
Component {
id: checkbox
StatusCheckBox {
checked: root.selected
onCheckedChanged: {
root.selected = checked
root.checkedChanged(checked)
}
}
}
}// End of Content item
@@ -4,15 +4,91 @@ import QtQuick.Controls 2.14
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
/*!
\qmltype StatusPasswordStrengthIndicator
\inherits StatusProgressBar
\inqmlmodule StatusQ.Controls
\since StatusQ.Controls 0.1
\brief It allows visualizing the password strength. Inherits from StatusProgressBar.
The \c StatusPasswordStrengthIndicator changes its text and color depending on the selected StatusPasswordStrengthIndicator::strength.
Since it inherits from \c StatusProgressBar, `value` property will change the length of the bar accordingly.
When `value` property is 0, StatusPasswordStrengthIndicator::strength is automatically set to StatusPasswordStrengthIndicator.Strength.None and no text is displayed.
Example of how the control looks like:
\image status_password_strength_indicator.png
Example of how to use it:
\qml
StatusPasswordStrengthIndicator {
id: weakPw
strength: StatusPasswordStrengthIndicator.Strength.Weak
value: 0.5
}
StatusPasswordStrengthIndicator {
id: sosoPw
strength: StatusPasswordStrengthIndicator.Strength.SoSo
value: 0.25
}
\endqml
For a list of components available see StatusQ.
*/
StatusProgressBar {
id: control
/*!
\qmlproperty string StatusPasswordStrengthIndicator::strength
This property holds the password strength value. Possible values are:
\list
\li StatusPasswordStrengthIndicator.Strength.None
\li StatusPasswordStrengthIndicator.Strength.VeryWeak
\li StatusPasswordStrengthIndicator.Strength.Weak
\li StatusPasswordStrengthIndicator.Strength.SoSo
\li StatusPasswordStrengthIndicator.Strength.Good
\li StatusPasswordStrengthIndicator.Strength.Great
\endlist
*/
property var strength
property string labelVeryWeak: "Very weak"
property string labelWeak: "Weak"
property string labelSoso: "So-so"
property string labelGood: "Good"
property string labelGreat: "Great"
/*!
\qmlproperty string StatusPasswordStrengthIndicator::labelVeryWeak
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.VeryWeak.
Default value: "Very weak"
*/
property string labelVeryWeak: qsTr("Very weak")
/*!
\qmlproperty string StatusPasswordStrengthIndicator::labelWeak
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.Weak.
Default value: "Weak"
*/
property string labelWeak: qsTr("Weak")
/*!
\qmlproperty string StatusPasswordStrengthIndicator::labelSoso
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.SoSo.
Default value: "So-so"
*/
property string labelSoso: qsTr("So-so")
/*!
\qmlproperty string StatusPasswordStrengthIndicator::labelGood
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.Good.
Default value: "Good"
*/
property string labelGood: qsTr("Good")
/*!
\qmlproperty string StatusPasswordStrengthIndicator::labelGreat
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.Great.
Default value: "Great"
*/
property string labelGreat: qsTr("Great")
enum Strength {
None, // 0
+50 -19
View File
@@ -11,7 +11,14 @@ Button {
property color bgColor: Theme.palette.baseColor2
property color contentColor: Theme.palette.baseColor1
signal clicked()
property var type: StatusPickerButton.Type.Next
property int lateralMargins: 16
property int textPixelSize: 15
enum Type {
Next,
Down
}
background: Item {
anchors.fill: parent
@@ -20,31 +27,57 @@ Button {
anchors.fill: parent
radius: 8
color: root.bgColor
}
StatusIcon {
id: nextIcon
anchors.right: parent.right
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
icon: "next"
color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1)
? root.contentColor : Theme.palette.directColor1
}
}
}
contentItem: Item {
anchors.fill: parent
state: root.type === StatusPickerButton.Type.Next ? "NEXT" : "DOWN"
StatusBaseText {
id: textLabel
anchors {
fill: parent
leftMargin: 16
}
anchors.verticalCenter: parent.verticalCenter
width: parent.width - icon.width - anchors.rightMargin - anchors.leftMargin - icon.anchors.rightMargin - icon.anchors.leftMargin
verticalAlignment: Text.AlignVCenter
font.pixelSize: 15
font.pixelSize: root.textPixelSize
color: root.contentColor
text: root.text
clip: true
elide: Text.ElideRight
}
StatusIcon {
id: icon
anchors.verticalCenter: parent.verticalCenter
color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1) ? root.contentColor : Theme.palette.directColor1
}
states: [
State {
name: "NEXT"
PropertyChanges {target: icon; icon: "next"}
PropertyChanges {target: icon; anchors.left: undefined }
PropertyChanges {target: icon; anchors.right: parent.right }
PropertyChanges {target: icon; anchors.rightMargin: root.lateralMargins / 2 }
PropertyChanges {target: icon; anchors.leftMargin: root.lateralMargins / 2 }
PropertyChanges {target: textLabel; anchors.left: parent.left }
PropertyChanges {target: textLabel; anchors.right: undefined }
PropertyChanges {target: textLabel; anchors.rightMargin: undefined }
PropertyChanges {target: textLabel; anchors.leftMargin: root.lateralMargins }
},
State {
name: "DOWN"
PropertyChanges {target: icon; icon: "chevron-down"}
PropertyChanges {target: icon; anchors.left: parent.left }
PropertyChanges {target: icon; anchors.right: undefined }
PropertyChanges {target: icon; anchors.rightMargin: root.lateralMargins / 2 }
PropertyChanges {target: icon; anchors.leftMargin: root.lateralMargins }
PropertyChanges {target: textLabel; anchors.left: icon.right }
PropertyChanges {target: textLabel; anchors.right: undefined }
PropertyChanges {target: textLabel; anchors.rightMargin: root.lateralMargins / 2 }
PropertyChanges {target: textLabel; anchors.leftMargin: undefined }
}
]
}
MouseArea {
@@ -52,8 +85,6 @@ Button {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.clicked();
}
onClicked: { root.clicked() }
}
}
@@ -34,10 +34,24 @@ ProgressBar {
radius: 5
StatusBaseText {
id: textItem
anchors.centerIn: parent
property bool _fittedInBar: width < bar.width ? true : false
text: control.text
font.pixelSize: 12
color: Theme.palette.indirectColor1
Component.onCompleted: opacity = width < bar.width ? 1 : 0
on_FittedInBarChanged: {
if (_fittedInBar && opacity == 0) {
fadeIn.start();
}
else if (!_fittedInBar) {
fadeIn.stop();
opacity = 0;
}
}
OpacityAnimator { id: fadeIn; target: textItem; from: 0; to: 1; duration: 250 }
}
}
}
+138 -14
View File
@@ -7,16 +7,101 @@ import StatusQ.Popups 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
/*!
\qmltype StatusSeedPhraseInput
\inherits Item
\inqmlmodule StatusQ.Controls
\since StatusQ.Controls 0.1
\brief Displays a text input with suggestions. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
The \c StatusSeedPhraseInput item displays a text input with suggestions filtered based on the typed text.
For example:
\qml
StatusSeedPhraseInput {
id: statusSeedInput
anchors.left: parent.left
anchors.right: parent.right
textEdit.input.anchors.leftMargin: 16
textEdit.input.anchors.rightMargin: 16
textEdit.input.anchors.topMargin: 11
leftComponentText: "1"
inputList: ListModel {
ListElement {
seedWord: "panda"
}
ListElement {
seedWord: "posible"
}
ListElement {
seedWord: "wing"
}
}
}
\endqml
\image status_seed_phrase_input.png
For a list of components available see StatusQ.
*/
Item {
id: root
width: 162
height: 44
/*!
\qmlproperty alias StatusSeedPhraseInput::textEdit
This property is an alias to the StatusInput's textEdit property.
*/
property alias textEdit: seedWordInput
/*!
\qmlproperty alias StatusSeedPhraseInput::text
This property is an alias to the StatusInput's text property.
*/
property alias text: seedWordInput.text
/*!
\qmlproperty string StatusSeedPhraseInput::leftComponentText
This property sets the StatusInput's left component's text.
*/
property string leftComponentText: ""
/*!
\qmlproperty ListModel StatusSeedPhraseInput::inputList
This property holds the filtered words list based on the user's
input text.
*/
property ListModel inputList: ListModel { }
/*!
\qmlproperty ListModel StatusSeedPhraseInput::filteredList
This signal is emitted when the user selects a word from
the suggestions list, either by clicking on it or by completing
typing 4 charactersnd passes as a parameter the selected word.
The corresponding handler is \c onDoneInsertingWord
*/
property ListModel filteredList: ListModel { }
/*!
\qmlsignal doneInsertingWord
This signal is emitted when the user selects a word from the suggestions list
either by mouse click or by typing 4 characters that match and passes as a parameter
the selected word. The corresponding handler is \c onDoneInsertingWord.
*/
signal doneInsertingWord(string word)
/*!
\qmlsignal keyPressed
This signal is emitted when the user presses a keyboard key and passes as a
parameter the event. The corresponding handler is \c onKeyPressed.
*/
signal keyPressed(var event)
/*!
\qmlsignal editClicked
This signal is emitted when the user clicks inside the StatusInput.
The corresponding handler is \c onEditClicked
*/
signal editClicked()
function setWord(word) {
seedWordInput.ignoreTextChange = true
seedWordInput.input.edit.text = word
}
onActiveFocusChanged: {
if (root.activeFocus) {
@@ -25,38 +110,66 @@ Item {
}
StatusInput {
property bool ignoreTextChange: false
id: seedWordInput
implicitWidth: parent.width
implicitHeight: parent.height
input.anchors.topMargin: 0
input.anchors.leftMargin: 0
input.anchors.rightMargin: 0
input.anchors.bottom: input.parent.bottom
input.leftComponent: StatusBaseText {
text: root.leftComponentText
color: seedWordInput.input.edit.activeFocus ?
Theme.palette.primaryColor1 : Theme.palette.baseColor1
font.pixelSize: 15
}
input.acceptReturn: true
onTextChanged: {
if (ignoreTextChange) {
ignoreTextChange = false
return
}
filteredList.clear();
if (text !== "") {
for (var i = 0; i < inputList.count;i++) {
if (inputList.get(i).seedWord.startsWith(text)) {
let textToCheck = text.trim()
if (textToCheck !== "") {
for (var i = 0; i < inputList.count; i++) {
if (inputList.get(i).seedWord.startsWith(textToCheck)) {
filteredList.insert(filteredList.count, {"seedWord": inputList.get(i).seedWord});
}
}
seedSuggestionsList.model = filteredList;
if ((text.length === 3) && (filteredList.count === 1) &&
((input.edit.keyEvent !== Qt.Key_Backspace) && (input.edit.keyEvent !== Qt.Key_Delete))) {
seedWordInput.text = filteredList.get(0).seedWord;
seedWordInput.input.edit.cursorPosition = seedWordInput.text.length;
if (filteredList.count === 1 && input.edit.keyEvent !== Qt.Key_Backspace
&& input.edit.keyEvent !== Qt.Key_Delete
&& filteredList.get(0).seedWord.trim() === textToCheck) {
seedWordInput.input.edit.cursorPosition = textToCheck.length;
seedSuggestionsList.model = 0;
root.doneInsertingWord(seedWordInput.text);
root.doneInsertingWord(textToCheck);
}
} else {
seedSuggestionsList.model = 0;
}
}
onKeyPressed: {
if (input.edit.keyEvent === Qt.Key_Tab || input.edit.keyEvent === Qt.Key_Return || input.edit.keyEvent === Qt.Key_Enter) {
if (!!text && seedSuggestionsList.count > 0) {
seedSuggestionsList.completeWordFill(filteredList.get(seedSuggestionsList.currentIndex).seedWord)
event.accepted = true
return
}
}
if (input.edit.keyEvent === Qt.Key_Down) {
seedSuggestionsList.incrementCurrentIndex()
}
if (input.edit.keyEvent === Qt.Key_Up) {
seedSuggestionsList.decrementCurrentIndex()
}
root.keyPressed(event);
}
onEditClicked: {
root.editClicked();
}
}
Item {
@@ -93,6 +206,19 @@ Item {
anchors.topMargin: 8
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
onCountChanged: {
seedSuggestionsList.currentIndex = 0
}
function completeWordFill(seedWord) {
let seedWordTrimmed = seedWord.trim();
root.setWord(seedWordTrimmed);
seedWordInput.input.edit.cursorPosition = seedWordInput.text.length;
seedSuggestionsList.model = 0;
root.doneInsertingWord(seedWordTrimmed);
}
clip: true
ScrollBar.vertical: ScrollBar { }
delegate: Item {
@@ -102,7 +228,8 @@ Item {
Rectangle {
width: seedSuggestionsList.width
height: parent.height
color: mouseArea.containsMouse? Theme.palette.primaryColor1 : "transparent"
color: mouseArea.containsMouse || index === seedSuggestionsList.currentIndex ?
Theme.palette.primaryColor1 : "transparent"
}
StatusBaseText {
id: suggWord
@@ -110,7 +237,7 @@ Item {
anchors.leftMargin: 14
anchors.verticalCenter: parent.verticalCenter
text: seedWord
color: mouseArea.containsMouse ? Theme.palette.indirectColor1 : Theme.palette.directColor1
color: mouseArea.containsMouse || index === seedSuggestionsList.currentIndex ? Theme.palette.indirectColor1 : Theme.palette.directColor1
font.pixelSize: 13
elide: Text.ElideRight
}
@@ -121,10 +248,7 @@ Item {
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
seedWordInput.text = seedWord;
seedWordInput.input.edit.cursorPosition = seedWordInput.text.length;
root.doneInsertingWord(seedWordInput.text);
seedSuggestionsList.model = 0;
seedSuggestionsList.completeWordFill(seedWord)
}
}
}
@@ -23,7 +23,7 @@ Item {
property alias color: edit.color
property alias font: edit.font
property alias focussed: edit.activeFocus
property alias verticalAlignmet: edit.verticalAlignment
property alias verticalAlignment: edit.verticalAlignment
property alias horizontalAlignment: edit.horizontalAlignment
signal linkActivated(string link)
+14 -16
View File
@@ -6,37 +6,35 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
/*!
/note beware, the slider processes the mouse events only in its control space. So it must be at least as high
as the /c handle so user can grab it fully.
*/
Slider {
id: statusSlider
implicitWidth: 360
implicitHeight: 4
implicitHeight: Math.max(handle.implicitHeight, background.implicitHeight)
leftPadding: 0
background: Rectangle {
id: bgRect
x: statusSlider.leftPadding
y: statusSlider.topPadding + statusSlider.availableHeight / 2 - height / 2
implicitWidth: 100
implicitHeight: 4
width: statusSlider.availableWidth
height: implicitHeight
color: {
if (statusSlider.value === statusSlider.to) {
return Theme.palette.primaryColor1
}
return Theme.palette.primaryColor3
}
color: Theme.palette.primaryColor3
radius: 2
Rectangle {
width: statusSlider.visualPosition * parent.width
height: parent.height
color: Theme.palette.primaryColor1
radius: 2
anchors.fill: parent
visible: statusSlider.value > statusSlider.from && statusSlider.value < statusSlider.to
gradient: Gradient {
GradientStop { color: Theme.palette.primaryColor1 ; position: 0 }
GradientStop { color: Theme.palette.primaryColor3 ; position: (((statusSlider.value - statusSlider.from) * 100) / (statusSlider.to - statusSlider.from) / 100).toFixed(2) }
GradientStop { color: Theme.palette.primaryColor1 ; position: (((statusSlider.value - statusSlider.from) * 100) / (statusSlider.to - statusSlider.from) / 100).toFixed(2) }
GradientStop { color: Theme.palette.primaryColor3 ; position: 1 }
orientation: Gradient.Horizontal
}
}
} // background
+1 -1
View File
@@ -8,7 +8,7 @@ TabBar {
background: Rectangle {
implicitHeight: 36
color: Theme.palette.directColor7
color: Theme.palette.statusSwitchTab.barBackgroundColor
radius: 8
}
}
@@ -22,7 +22,9 @@ TabButton {
StatusBaseText {
id: label
text: statusSwitchTabButton.text
color: Theme.palette.primaryColor1
color: statusSwitchTabButton.checked ?
Theme.palette.statusSwitchTab.selectedTextColor :
Theme.palette.statusSwitchTab.textColor
font.weight: Font.Medium
font.pixelSize: 15
horizontalAlignment: Text.AlignHCenter
@@ -36,7 +38,7 @@ TabButton {
implicitHeight: 36
implicitWidth: 148
color: statusSwitchTabButton.checked ?
Theme.palette.statusSwitchTab.backgroundColor :
Theme.palette.statusSwitchTab.buttonBackgroundColor :
"transparent"
radius: 8
layer.enabled: true
@@ -10,7 +10,7 @@ import StatusQ.Controls 0.1
Item {
id: control
property string selectedColor
property string label: "Account color"
property string label: qsTr("Account color")
property var model
property bool enabled: true
@@ -45,7 +45,7 @@ Item {
return true
}
// Check the colors in the other theme
const currentColor = Utils.getThemeAccountColor(upperCaseColor, Theme.palette.accountColors)
const currentColor = Utils.getThemeAccountColor(upperCaseColor, Theme.palette.userCustomizationColors)
if (!currentColor) {
return false
}
@@ -4,7 +4,7 @@ import StatusQ.Core.Utils 0.1
StatusValidator {
name: "addressOrEns"
errorMessage: "Please enter a valid address or ENS name."
errorMessage: qsTr("Please enter a valid address or ENS name.")
validate: function (t) {
return Utils.isValidAddress(t) || Utils.isValidEns(t) ?
@@ -4,7 +4,7 @@ import StatusQ.Core.Utils 0.1
StatusValidator {
name: "address"
errorMessage: "Please enter a valid address."
errorMessage: qsTr("Please enter a valid address.")
validate: function (t) {
return Utils.isValidAddress(t) ? true : { actual: t }
@@ -6,7 +6,7 @@ StatusAsyncValidator {
name: "asyncEns"
errorMessage: "ENS name could not be resolved in to an address"
errorMessage: qsTr("ENS name could not be resolved in to an address")
validate: function (asyncResult) {
return Utils.isValidAddress(asyncResult)
@@ -5,7 +5,7 @@ Item {
id: statusAsyncValidator
property string name: ""
property string errorMessage: "invalid input"
property string errorMessage: qsTr("invalid input")
signal asyncComplete(var result)
signal validationComplete(var value, bool valid)
@@ -45,10 +45,10 @@ StatusValidator {
property real top
name: "floatValidator"
errorMessage: "Please enter a valid numeric value."
errorMessage: qsTr("Please enter a valid numeric value.")
validate: function (t) {
return !isNaN(t) && t > bottom && t <= top ? true : {
return !isNaN(t) && t >= bottom && t <= top ? true : {
bottom: bottom,
top: top,
actual: t
@@ -47,7 +47,7 @@ StatusValidator {
property int top
name: "intValidator"
errorMessage: "Please enter a valid numeric value."
errorMessage: qsTr("Please enter a valid numeric value.")
validatorObj: IntValidator { bottom: root.bottom; locale: root.locale; top: root.top }
validate: function (t) {
@@ -5,7 +5,7 @@ StatusValidator {
name: "url"
errorMessage: "Please enter a valid URL"
errorMessage: qsTr("Please enter a valid URL")
validate: function (value) {
return Utils.isURL(value);
@@ -5,7 +5,7 @@ QtObject {
id: statusValidator
property string name: ""
property string errorMessage: "invalid input"
property string errorMessage: qsTr("invalid input")
property var validatorObj
property var validate: function (value) {
+2
View File
@@ -9,6 +9,7 @@ StatusChatListCategoryItemButton 0.1 StatusChatListCategoryItemButton.qml
StatusColorSelector 0.1 StatusColorSelector.qml
StatusIconTabButton 0.1 StatusIconTabButton.qml
StatusIdenticonRing 0.1 StatusIdenticonRing.qml
StatusItemPicker 0.1 StatusItemPicker.qml
StatusNavBarTabButton 0.1 StatusNavBarTabButton.qml
StatusTabBarIconButton 0.1 StatusTabBarIconButton.qml
StatusToolTip 0.1 StatusToolTip.qml
@@ -36,3 +37,4 @@ StatusWalletColorButton 0.1 StatusWalletColorButton.qml
StatusWalletColorSelect 0.1 StatusWalletColorSelect.qml
StatusColorSelectorGrid 0.1 StatusColorSelectorGrid.qml
StatusSeedPhraseInput 0.1 StatusSeedPhraseInput.qml
StatusImageCrop 0.1 StatusImageCrop.qml
+1 -1
View File
@@ -14,7 +14,7 @@ import StatusQ.Core.Theme 0.1
\qml
StatusBaseText {
width: 240
text: "Hello World!"
text: qsTr("Hello World!")
font.pixelSize: 24
color: Theme.pallete.directColor1
}
+8 -4
View File
@@ -2,20 +2,24 @@ import QtQuick 2.13
import StatusQ.Core 0.1
QtObject {
id: statusIconSettings
id: root
property string name
property real width
property real height
property color color
property color textColor: Qt.rgba(255, 255, 255, 0.7)
property color disabledColor
property url source
property int rotation
property bool isLetterIdenticon
property int letterSize
property int letterSize: emoji ? 11 : (charactersLen == 1 ? _oneLetterSize : _twoLettersSize)
property int charactersLen: 1
property string emoji
property string emojiSize
property string emojiSize: _emojiSize
property StatusIconBackgroundSettings background: StatusIconBackgroundSettings {}
// can't have QtObject { id: d } inside QtObject, using '_' to indicate private
readonly property int _oneLetterSize: Math.max(15, root.width / 2.0)
readonly property int _twoLettersSize: Math.max(12, root.width / 2.2)
readonly property string _emojiSize: "%1x%1".arg(Math.max(16, root.width * 0.6))
}
+17 -10
View File
@@ -147,15 +147,19 @@ ThemePalette {
miscColor11: getColor('yellow2')
miscColor12: getColor('green6')
accountColors: [
getColor('blue5'),
getColor('yellow2'),
"#E6ABFC",
getColor('moss2'),
"#FB8383",
getColor('green4'),
"#ADA3FF",
getColor('brown3')
userCustomizationColors: [
"#AAC6FF",
"#887AF9",
"#51D0F0",
"#D37EF4",
"#FA6565",
"#FFCA0F",
"#93DB33",
"#10A88E",
"#AD4343",
"#EAD27B",
"silver", // update me when figma is ready
"darkgrey", // update me when figma is ready
]
identiconRingColors: ["#000000", "#726F6F", "#C4C4C4", "#E7E7E7", "#FFFFFF", "#00FF00",
@@ -226,7 +230,10 @@ ThemePalette {
}
property QtObject statusSwitchTab: QtObject {
property color backgroundColor: baseColor3
property color buttonBackgroundColor: primaryColor1
property color barBackgroundColor: primaryColor3
property color selectedTextColor: white
property color textColor: primaryColor1
}
property QtObject statusSelect: QtObject {
+17 -10
View File
@@ -145,15 +145,19 @@ ThemePalette {
miscColor11: getColor('brown2')
miscColor12: getColor('green5')
accountColors: [
getColor('blue'),
getColor('brown2'),
getColor('violet'),
"#1D806F",
getColor('red2'),
getColor('green2'),
getColor('purple'),
getColor('brown')
userCustomizationColors: [
"#2946C4",
"#887AF9",
"#51D0F0",
"#D37EF4",
"#FA6565",
"#FFCA0F",
"#7CDA00",
"#26A69A",
"#8B3131",
"#9B832F",
"silver", // update me when figma is ready
"darkgrey", // update me when figma is ready
]
identiconRingColors: ["#000000", "#726F6F", "#C4C4C4", "#E7E7E7", "#FFFFFF", "#00FF00",
@@ -224,7 +228,10 @@ ThemePalette {
}
property QtObject statusSwitchTab: QtObject {
property color backgroundColor: white
property color buttonBackgroundColor: primaryColor1
property color barBackgroundColor: primaryColor3
property color selectedTextColor: white
property color textColor: primaryColor1
}
property QtObject statusSelect: QtObject {
+6 -3
View File
@@ -1,7 +1,7 @@
import QtQuick 2.13
QtObject {
id: theme
property string name
@@ -90,7 +90,7 @@ QtObject {
property color miscColor11
property color miscColor12
property var accountColors: []
property var userCustomizationColors: []
property var identiconRingColors: []
@@ -156,7 +156,10 @@ QtObject {
}
property QtObject statusSwitchTab: QtObject {
property color backgroundColor
property color buttonBackgroundColor
property color barBackgroundColor
property color selectedTextColor
property color textColor
}
property QtObject statusSelect: QtObject {
+20 -6
View File
@@ -14,17 +14,26 @@ QtObject {
"small": "18x18",
"verySmall": "16x16"
}
readonly property var format: {
"png": "png",
"svg": "svg"
}
property string base: Qt.resolvedUrl("../../../assets/twemoji/")
function parse(text, renderSize = size.small) {
function parse(text, renderSize = size.small, renderFormat = format.svg) {
const renderSizes = renderSize.split("x");
if (!renderSize.includes("x") || renderSizes.length !== 2) {
throw new Error("Invalid value for 'renderSize' parameter: ", renderSize);
}
Twemoji.twemoji.base = base
Twemoji.twemoji.ext = ".svg"
Twemoji.twemoji.size = "svg"
const path = renderFormat == format.svg ? "svg/" : "72x72/"
Twemoji.twemoji.base = base + path
Twemoji.twemoji.ext = `.${renderFormat}`
return Twemoji.twemoji.parse(text, {
callback: (iconId, options) => {
return options.base + iconId + options.ext;
},
attributes: function() {
return {
width: renderSizes[0],
@@ -34,6 +43,10 @@ QtObject {
}
})
}
function iconId(text) {
const parsed = parse(text);
return parsed.match('src=".*\/(.+?).svg')[1]
}
function fromCodePoint(value) {
return Twemoji.twemoji.convert.fromCodePoint(value)
}
@@ -98,7 +111,7 @@ QtObject {
return undefined
}
function getRandomEmoji() {
function getRandomEmoji(size) {
var randomEmoji = EmojiJSON.emoji_json[Math.floor(Math.random() * EmojiJSON.emoji_json.length)]
const extenstionIndex = randomEmoji.unicode.lastIndexOf('.');
@@ -115,6 +128,7 @@ QtObject {
})
const encodedIcon = String.fromCodePoint(...codePointParts);
return Emoji.parse(encodedIcon) + ' '
// Adding a space because otherwise, some emojis would fuse since emoji is just a string
return Emoji.parse(encodedIcon, size || undefined) + ' '
}
}
@@ -10,7 +10,7 @@ Rectangle {
id: root
property string chatId: ""
property string name: "channelName"
property string message: "My latest message\n with a return"
property string message: qsTr("My latest message\n with a return")
property string identicon: ""
// TODO: what about dark theme?
@@ -22,7 +22,7 @@ Rectangle {
Loader {
id: identicon
sourceComponent: root.identicon === "" ? statusIdenticon : userOrChannelIdenticon
sourceComponent: root.identicon === "" ? statusIdenticon : userOrChannelIdenticon
anchors.left: parent.left
anchors.leftMargin: 16
anchors.verticalCenter: parent.verticalCenter
@@ -120,7 +120,7 @@ Rectangle {
font.pixelSize: 14
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
text: "Open"
text: qsTr("Open")
color: "black"
}
}
+5 -5
View File
@@ -39,10 +39,10 @@ import "statusModal" as Spares
]
rightButtons: [
StatusButton {
text: "Button"
text: qsTr("Button")
},
StatusButton {
text: "Button"
text: qsTr("Button")
}
]
}
@@ -87,7 +87,7 @@ QC.Popup {
border.width: 1
StatusBaseText {
anchors.centerIn: parent
text: "Add any header here"
text: qsTr("Add any header here")
font.pixelSize: 15
color: Theme.palette.directColor1
}
@@ -115,7 +115,7 @@ QC.Popup {
border.width: 1
StatusBaseText {
anchors.centerIn: parent
text: "Add any footer here"
text: qsTr("Add any footer here")
font.pixelSize: 15
color: Theme.palette.directColor1
}
@@ -137,7 +137,7 @@ QC.Popup {
This property exposes the different properties of the standard header.
\endqml
*/
property StatusModalHeaderSettings header: StatusModalHeaderSettings {}
property StatusModalHeaderSettings header: StatusModalHeaderSettings {}
/*!
\qmlproperty rightButtons
This property helps user assign the right buttons on the footer.
@@ -7,6 +7,7 @@ import StatusQ.Popups 0.1
StatusPopupMenu {
id: root
dim: false
property var searchPopup
property var locationModel
@@ -15,7 +16,7 @@ StatusPopupMenu {
signal itemClicked(string firstLevelItemValue, string secondLevelItemValue)
StatusMenuItem {
text: "Anywhere"
text: qsTr("Anywhere")
onTriggered: {
searchPopup.resetSearchSelection();
root.itemClicked("", "");
@@ -36,7 +37,6 @@ StatusPopupMenu {
item.parentIconName = model.iconName;
item.parentImageSource = model.imageSource;
item.parentIdenticonColor = !!model.iconColor ? model.iconColor : Theme.palette.primaryColor1;
item.parentIsIdenticon = model.isIdenticon;
root.subMenuItemIcons.push({
source: model.imageSource,
icon: model.iconName,
@@ -80,6 +80,7 @@ StatusPopupMenu {
id: subMenus
StatusPopupMenu {
id: menu
dim: false
property var subItemsModel
property string parentValue
property string parentIconName
@@ -103,7 +104,6 @@ StatusPopupMenu {
image.source: model.imageSource
iconSettings.name: model.iconName
iconSettings.color: model.iconColor
image.isIdenticon: model.isIdenticon
onTriggered: {
searchPopup.resetSearchSelection()
if (menuLoader.parentTitleText === "Chat") {
+6 -4
View File
@@ -18,9 +18,9 @@ StatusModal {
showFooter: false
property string searchText: contentItem.searchText
property string noResultsLabel: "No results"
property string defaultSearchLocationText: "Anywhere"
property bool loading
property string noResultsLabel: qsTr("No results")
property string defaultSearchLocationText: qsTr("Anywhere")
property bool loading: false
property Menu searchOptionsPopupMenu: Menu { }
property var searchResults: [ ]
property var searchSelectionButton
@@ -95,6 +95,8 @@ StatusModal {
anchors.verticalCenter: parent.verticalCenter
focus: true
font.pixelSize: 28
topPadding: 5 //smaller padding to handle bigger font
bottomPadding: 5
clearable: true
showBackground: false
font.family: Theme.palette.baseFont.name
@@ -119,7 +121,7 @@ StatusModal {
root.searchSelectionButton = searchOptionsMenuButton
}
property string prefixText: "In"
property string prefixText: qsTr("In")
property string primaryText: ""
property string secondaryText: ""
property StatusIconSettings iconSettings: StatusIconSettings {
+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 10.75C16 11.1642 16.3383 11.4934 16.7469 11.5617C18.8767 11.9175 20.5 13.7692 20.5 16C20.5 18.4853 18.4853 20.5 16 20.5H8C5.51472 20.5 3.5 18.4853 3.5 16C3.5 13.7692 5.12331 11.9175 7.25314 11.5617C7.66169 11.4934 8 11.1642 8 10.75C8 10.3358 7.66253 9.99508 7.25149 10.0462C4.29103 10.4146 2 12.9398 2 16C2 19.3137 4.68629 22 8 22H16C19.3137 22 22 19.3137 22 16C22 12.9398 19.709 10.4146 16.7485 10.0462C16.3375 9.99508 16 10.3358 16 10.75Z" fill="black"/>
<path d="M12.75 6.01777C12.75 5.57232 13.2886 5.34923 13.6036 5.66421L15.4697 7.53033C15.7626 7.82322 16.2374 7.82322 16.5303 7.53033C16.8232 7.23744 16.8232 6.76256 16.5303 6.46967L12.5303 2.46967C12.2374 2.17678 11.7626 2.17678 11.4697 2.46967L7.46967 6.46967C7.17678 6.76256 7.17678 7.23744 7.46967 7.53033C7.76256 7.82322 8.23744 7.82322 8.53033 7.53033L10.3964 5.66421C10.7114 5.34923 11.25 5.57232 11.25 6.01777V16C11.25 16.4142 11.5858 16.75 12 16.75C12.4142 16.75 12.75 16.4142 12.75 16V6.01777Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,4 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.281 20C14.9447 20 14.7031 20.3279 14.7344 20.6628C14.7444 20.7703 14.7499 20.8827 14.7499 21C14.7499 22.3889 13.5436 23.75 11.9999 23.75C10.4563 23.75 9.24992 22.3889 9.24992 21C9.24992 20.8827 9.25542 20.7703 9.26547 20.6628C9.29677 20.3279 9.05515 20 8.71885 20H5.52103C3.78474 20 2.87319 17.9394 4.04115 16.6547L5.218 15.3601C5.72872 14.7983 6.069 14.1028 6.19909 13.3548L7.31456 6.94085C7.71072 4.66293 9.68781 3 11.9999 3C14.312 3 16.2891 4.66293 16.6853 6.94085L17.8008 13.3548C17.9308 14.1028 18.2711 14.7983 18.7818 15.3601L19.9587 16.6547C21.1266 17.9394 20.2151 20 18.4788 20H15.281ZM11.0322 20.0283L11.0313 20.0293L11.039 20.0214L11.0415 20.0188C11.0427 20.0175 11.0433 20.0168 11.0434 20.0167C11.0438 20.0163 11.0434 20.0167 11.0434 20.0167L11.0415 20.0188C11.0525 20.0073 11.0681 20 11.084 20H12.9159C12.9318 20 12.9469 20.0068 12.9579 20.0183C12.9608 20.0217 12.9679 20.0302 12.978 20.0438C13.001 20.0744 13.0389 20.13 13.0791 20.2104C13.1582 20.3687 13.2499 20.6296 13.2499 21C13.2499 21.6111 12.6654 22.25 11.9999 22.25C11.3344 22.25 10.7499 21.6111 10.7499 21C10.7499 20.6296 10.8416 20.3687 10.9207 20.2104C10.9609 20.13 10.9988 20.0744 11.0218 20.0438C11.032 20.0302 11.0386 20.0222 11.0415 20.0188L11.039 20.0214L11.0372 20.0233L11.034 20.0265L11.0322 20.0283ZM5.15106 17.6637L6.32791 16.3691C7.03015 15.5967 7.49804 14.6403 7.67691 13.6118L8.79238 7.19786C9.06358 5.63842 10.4171 4.5 11.9999 4.5C13.5828 4.5 14.9363 5.63842 15.2075 7.19786L16.3229 13.6118C16.5018 14.6403 16.9697 15.5967 17.6719 16.3691L18.8488 17.6637C19.1408 17.9849 18.9129 18.5 18.4788 18.5H5.52103C5.08696 18.5 4.85907 17.9849 5.15106 17.6637Z" fill="black"/>
<rect x="0.585786" y="20.7988" width="28" height="4" rx="2" transform="rotate(-45 0.585786 20.7988)" fill="black" stroke="white" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 18.5C14.6944 18.5 18.5 14.6944 18.5 10C18.5 5.30558 14.6944 1.5 10 1.5C5.30558 1.5 1.5 5.30558 1.5 10C1.5 14.6944 5.30558 18.5 10 18.5ZM10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20Z" fill="#4360DF"/>
<path d="M7.56858 13.1746C7.22903 12.9373 6.75712 12.9308 6.46422 13.2237C6.17133 13.5166 6.16876 13.996 6.49708 14.2485C7.46695 14.9946 8.68155 15.4381 9.99976 15.4381C11.318 15.4381 12.5326 14.9946 13.5024 14.2485C13.8308 13.996 13.8282 13.5166 13.5353 13.2237C13.2424 12.9308 12.7705 12.9373 12.4309 13.1746C11.742 13.6558 10.9039 13.9381 9.99976 13.9381C9.09565 13.9381 8.25747 13.6558 7.56858 13.1746Z" fill="#4360DF"/>
<path d="M15 8.5C15 9.32843 14.3284 10 13.5 10C12.6716 10 12 9.32843 12 8.5C12 7.67157 12.6716 7 13.5 7C14.3284 7 15 7.67157 15 8.5Z" fill="#4360DF"/>
<path d="M8 8.5C8 9.32843 7.32843 10 6.5 10C5.67157 10 5 9.32843 5 8.5C5 7.67157 5.67157 7 6.5 7C7.32843 7 8 7.67157 8 8.5Z" fill="#4360DF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+76
View File
@@ -0,0 +1,76 @@
# Code examples
## Parent assumptions
### Don't assume parent's context bad example
`PreanchoredItem.qml` component
```qml
Item {
// This limits the usage of the component and can lead to maintenance burden
anchors.fill: parent
//...
}
```
Broken usage of `PreanchoredItem` component
```qml
Item {
id: root
// Using PreanchoredItem with a container (layouts, delegate in a ListView ..) will result in warning or broken UI layout.
ColumnLayout {
// This will generate a qml warning and Layout won't work as expected
PreanchoredItem {
Layout.fillWidth: true
Layout.fillHeight: true
}
// ....
}
}
```
### Give user choice
User should be allowed to choose how to size the control based on it's design and UX requirements. Only provide hints and recommendations.
`GoodItem.qml` component
```qml
Item {
// Rather provide implicit sizes if appropriate and leave user to layout or anchors as
// he see fit given its panel/view design
implicitWidth: 200
implicitHeight: 100
//...
}
```
Usage of `GoodItem` component
```qml
// Using GoodItem with a container (layouts, positioners, delegate in a ListView ..) works well
ColumnLayout {
// This will generate a qml warning and Layout won't work as expected
GoodItem {
Layout.fillWidth: true
Layout.fillHeight: true
}
GoodItem {
Layout.preferredWidth: true
Layout.preferredHeight: true
}
GoodItem {
// Nothing will still show the "optimal" size provided by the control
}
// ....
}
```
+167
View File
@@ -0,0 +1,167 @@
# Contributing guidelines
## Generic Reusable Component Design
Define the component's purpose. Narrow down the scope as much as possible.
Avoid assumptions about its parent/owner
- See [broken behavior example](./code-examples.md#Dont-assume-parents-context-bad-example)
- See [well-behaved behavior example](./code-examples.md#Give-user-choice)
Define and make clear the interface/contract that defines requirements.
To simplify maintenance consider the following options for implementing custom controls, generic containers or views:
- Use QML2's customization mechanics in place if the change is simple: [Check docs for examples](https://doc.qt.io/qt-6/qtquickcontrols2-customize.html)
- If the control should be generic or the changes are extensive and the design maps to a QML control, use QML2's customization mechanisms in a generic QML control: [Check docs for examples](https://doc.qt.io/qt-6/qtquickcontrols2-customize.html) instead of starting from scratch.
- Use Qt controls instead of defining our own. Defining and implementing a complex control from scratch should be the last resort and there should be an excellent reason to do that.
- For keyboard input item, ensure focus properties are set; see [Qt docs](https://doc.qt.io/qt-6/qtquick-input-focus.html)
When unsure, check Qt's excellent documentation.
- For main components/controls, there is a good overview that is worth revisiting from time to time
- Functions/Properties usually have a short on-point description
- In `QtCreator` you can quickly open the doc panel using `F1` key while having a cursor on component or function/property and recheck its invariants, pre and post conditions
Consider that design follows user-friendlier principles.
- Have commonly used items at hand
- Have transition if possible
- Use Qt's property animation for states
- Avoid often and radical size changes to items in views/layouts. Radical size change confuses users.
- If the content is not available, consider having placeholders using the estimated size
(delegates, dummy items)
Have the base control as the component's root. This way, control inherits all the interface and reduce the code.
If it doesn't map to an existing one, use a base control like `Item`.
- Don't use layouts or positioners as base for controls, they don't have the same behavior when used in layouts/containers
- Layouts have `fillWidth`/`fillHeight` as true by default and they will be extended. Controls don't and they will follow implicit sizes
## QML well-behaved components checklist
### Positioning and sizing
[Positioners and Layouts In QML](https://doc.qt.io/qt-6/qtquick-usecase-layouts.html)
- Define size hints appropriately. Define implicit properties (`impicitWidth` and `implicitHeight`)
- They are used to break polishing binding loops for adaptive control
- All the containers use the implicit size for deriving the initial size (Layouts, Popups, Delegates, GridView, ListView ...). Size hints combined with resize adaptability are the appropriate way to have reactive controls.
- For complex controls, look if layouts are the choice as the main position driver. [Layouts](###Layouts)
```qml
Item {
id: root
implicitWidth: mainLayout.implicitWidth
implicitHeight: mainLayout.implicitHeight
RowLayout { // Column, Grid
id: mainLayout
// ...
}
```
- Adapt to the requested size if control can scale or it make sense to be extensible. For sizes bigger than content follow QML way for similar controls. e.g. Text leaves empty space around content
- If the control is not adaptable and makes sense only in its full size, define default sizes and make it work by default with positioners (`Row`, `Column`, `Grid`).
- [Item Positioners in QML](https://doc.qt.io/qt-5/qtquick-positioning-layouts.html)
- Don't mix hints with sizes, it will create binding loops
### Quality
Pixel miss-alignment for sensitive elements like icons and images with straight and thin lines can degrade the quality perceptibly.
Dividing sizes and coordinates are the usual root cause of pixel miss-alignment. Correct subpixel division by rounding the result of the operation to a fixed value, e.g. `x: (width/2).toFixed()`
- Example of pixel miss-alignment. The right control has a position offset of `Qt.pos(0.7, 0.4)` therefore, the image interpolation generates a blurred rendering.
- ![](https://user-images.githubusercontent.com/47554641/163448378-3a6ede2f-a922-4f35-bff7-93c6bfa840d5.png)
## Topics
### QML Coding Conventions
[Follow Qt's way if appropriate](https://doc.qt.io/qt-6/qml-codingconventions.html)
### Layouts
[Qt Quick Layouts Overview](https://doc.qt.io/qt-6/qtquicklayouts-overview.html)
Hierarchically sources hints from children. Implicit properties will have the recommended aggregated size.
Layouts as children of other layouts have `fillWidth` and `fillHeight` enabled; controls don't.
Use `Layout.preferredWidth` and `Layout.preferredHeight` attached properties to overwrite item's `implicitWidth` if they are not appropriate to the design.
### Scope
Follow [Qt's recommendations](https://doc.qt.io/qt-5/qtqml-documents-scope.html) if appropriate
- Avoid dynamic scoping in out-of-line components, see more: https://doc.qt.io/qt-5/qtqml-documents-scope.html#component-instance-hierarchy
- Example
```qml
Item {
// Probably won't work as intended. If another `testComp` instance is defined in QML's document hierarchy model and has a `testProp` property, that will be sourced instead
property bool booleanProp: testComp.testProp
// Same behavior if the TestComponent is defined in a file TestComponent.qml
component TestComponent: Item {
id: testComp
property bool testProp: false
}
}
```
- Component `id`s are not accessible outside the component's scope. If required, the component can expose the instance through properties binding. E.g. `readonly property ComponentType exposedComponent: privateInstanceId`
- If in doubt, explicitly use an instance variable to access properties
- If the scope is clear and there is no ambiguity, use the property directly for readability
## Testing
Test in isolation
- Use `qmlproject`s and `qml` for quick complex scenarios
- Use [`QtQuick.TestCase`](https://doc.qt.io/qt-5/qtquicktest-index.html)
Integration tests
- Use sandbox test app
- Use QML `qmlproject`s and `qmlscene` for quick debugging with layouts
Try scenarios
- Embed in Layouts with different properties: fixed size, min-max, not size set
```qml
Window {
width: mainLayout.implicitWidth
heigh: mainLayout.implicitHeight
GridLayout {
id: mainLayout
rows: 3
column: 3
anchor.fill: parent
Label { text: "Fixed width" }
TestControl {
Layout.preferredWidth: 100
Layout.preferredHeight: 100
}
Label { text: "Fill space" }
TestControl {
Layout.fillWidth: true
Layout.fillHeight: true
}
Label { text: "Width range" }
TestControl {
Layout.minWidth: 50
Layout.maxWidth: 150
}
TestControl {}
}
}
```
- Resize window to check the behavior for each case
- Visually validate that each control is behaving as expected
- Add controls with different properties that affect control size behavior
+10444 -284
View File
File diff suppressed because it is too large Load Diff
+44
View File
@@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.5)
project(TestStatusQ LANGUAGES CXX)
enable_testing()
# TODO: Workaround until we make StatusQ a CMake library
list(APPEND QML_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../src/")
set(QML_IMPORT_PATH "${QML_DIRS}" CACHE STRING "Qt Creator extra qml import paths")
set(QML2_IMPORT_PATH "${QML_DIRS}" CACHE STRING "Qt Creator extra qml import paths")
find_package(QT NAMES Qt6 Qt5 COMPONENTS QuickTest Qml Quick REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS QuickTest Qml Quick REQUIRED)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# no need to copy around qml test files for shadow builds - just set the respective define
add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
add_executable(${PROJECT_NAME} main.cpp)
add_test(NAME ${PROJECT_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME} -input "${CMAKE_CURRENT_SOURCE_DIR}")
add_custom_target("Run_${PROJECT_NAME}" COMMAND ${CMAKE_CTEST_COMMAND} --test-dir "${CMAKE_CURRENT_BINARY_DIR}")
add_dependencies("Run_${PROJECT_NAME}" ${PROJECT_NAME})
# TODO: move this to a test helpers library
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
add_subdirectory(src)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::QuickTest
Qt${QT_VERSION_MAJOR}::Qml
Qt${QT_VERSION_MAJOR}::Quick
)
@@ -0,0 +1,100 @@
import QtQuick 2.0
import QtTest 1.0
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.TestHelpers 0.1
Item {
id: root
width: 400
height: 300
Component {
id: noSourceComponent
StatusImageCropPanel {
anchors.fill: parent
windowStyle: StatusImageCrop.WindowStyle.Rounded
}
}
property url testImageUrl: `${Qt.resolvedUrl(".")}../../sandbox/demoapp/data/logo-test-image.png`
Component {
id: withSourceComponent
StatusImageCropPanel {
anchors.fill: parent
// TODO: generate test image and break the sandbox dependency
source: root.testImageUrl
windowStyle: StatusImageCrop.WindowStyle.Rectangular
Component.onCompleted: setCropRect(Qt.rect(10, 0, sourceSize.width - 20, sourceSize.height))
}
}
Loader {
id: testLoader
anchors.fill: parent
active: false
}
TestCase {
id: qmlWarningsTest
name: "StatusImageCropPanel"
when: windowShown
//
// Test guards
function init() {
qtOuput.restartCapturing()
}
function cleanup() {
testLoader.active = false
}
//
// Tests
function test_no_source_initialization() {
testLoader.sourceComponent = noSourceComponent
testLoader.active = true
verify(waitForRendering(testLoader.item))
testLoader.active = false
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
}
function test_with_source_initialization() {
testLoader.sourceComponent = withSourceComponent
testLoader.active = true
verify(waitForRendering(testLoader.item))
testLoader.active = false
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
}
function test_setCrop_error_if_no_source_regression() {
testLoader.sourceComponent = noSourceComponent
testLoader.active = true
verify(waitForRendering(testLoader.item))
testLoader.item.setCropRect(Qt.rect(10, 10, 300, 300))
verify(qtOuput.qtOuput().length !== 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
qtOuput.restartCapturing()
testLoader.item.source = root.testImageUrl
verify(waitForRendering(testLoader.item))
testLoader.active = false
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
}
}
MonitorQtOutput {
id: qtOuput
}
}
-33
View File
@@ -1,33 +0,0 @@
cmake_minimum_required(VERSION 3.5)
project(TestStatusInputWithRegex LANGUAGES CXX)
enable_testing()
# TODO: Workaround until we make StatusQ a CMake library
list(APPEND QML_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../../src/")
set(QML_IMPORT_PATH "${QML_DIRS}" CACHE STRING "Qt Creator extra qml import paths")
set(QML2_IMPORT_PATH "${QML_DIRS}" CACHE STRING "Qt Creator extra qml import paths")
find_package(QT NAMES Qt6 Qt5 COMPONENTS QuickTest Qml REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS QuickTest Qml REQUIRED)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# no need to copy around qml test files for shadow builds - just set the respective define
add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
add_executable(TestStatusInputWithRegex main.cpp)
add_test(NAME TestStatusInputWithRegex COMMAND TestStatusInputWithRegex)
target_link_libraries(TestStatusInputWithRegex PRIVATE
Qt${QT_VERSION_MAJOR}::QuickTest
Qt${QT_VERSION_MAJOR}::Qml)
@@ -0,0 +1,53 @@
import QtQuick 2.0
import QtTest 1.0
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.TestHelpers 0.1
Item {
width: 300
height: 100
StatusBaseInput {
id: statusInput
text: "Control under test"
placeholderText: "Placeholder"
focus: true
}
TestCase {
id: testCase
name: "StatusBaseInput"
when: windowShown
//
// Test guards
function init() {
qtOuput.restartCapturing()
}
function cleanup() {
statusInput.text = ""
}
//
// Tests
function test_initial_empty_is_valid() {
mouseClick(statusInput)
// Do some editing
TestUtils.pressKeyAndWait(testCase, statusInput, Qt.Key_B)
TestUtils.pressKeyAndWait(testCase, statusInput, Qt.Key_Left)
TestUtils.pressKeyAndWait(testCase, statusInput, Qt.Key_A)
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
}
}
MonitorQtOutput {
id: qtOuput
}
}
@@ -0,0 +1,135 @@
import QtQuick 2.0
import QtTest 1.0
import StatusQ.Controls 0.1
import StatusQ.TestHelpers 0.1
Item {
width: 400
height: 300
Component {
id: noSourceComponent
StatusImageCrop {
anchors.fill: parent
windowStyle: StatusImageCrop.WindowStyle.Rounded
}
}
Component {
id: withSourceComponent
StatusImageCrop {
anchors.fill: parent
// TODO: generate test image and break the sandbox dependency
source: `${Qt.resolvedUrl(".")}../../sandbox/demoapp/data/logo-test-image.png`
windowStyle: StatusImageCrop.WindowStyle.Rectangular
Component.onCompleted: setCropRect(Qt.rect(10, 0, sourceSize.width - 20, sourceSize.height))
}
}
Loader {
id: testLoader
anchors.fill: parent
active: false
}
TestCase {
id: qmlWarningsTest
name: "StatusImageCrop-CheckQmlWarnings"
when: windowShown
//
// Test guards
function init() {
qtOuput.restartCapturing()
}
function cleanup() {
testLoader.active = false
}
//
// Tests
function test_no_source_initialization() {
testLoader.sourceComponent = noSourceComponent
testLoader.active = true
verify(waitForRendering(testLoader.item))
testLoader.active = false
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
}
function test_with_source_initialization() {
testLoader.sourceComponent = withSourceComponent
testLoader.active = true
verify(waitForRendering(testLoader.item))
testLoader.active = false
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
}
}
StatusImageCrop {
id: testControl
// TODO: generate test image and break the sandbox dependency
source: `${Qt.resolvedUrl(".")}../../sandbox/demoapp/data/logo-test-image.png`
windowStyle: StatusImageCrop.WindowStyle.Rectangular
Component.onCompleted: setCropRect(Qt.rect(10, 0, sourceSize.width - 20, sourceSize.height))
}
TestCase {
name: "StatusImageCrop-Functionality"
//
// Test guards
function init() {
qtOuput.restartCapturing()
}
function cleanup() {
}
//
// Tests
function test_inflateRect_by2() {
const refRect = Qt.rect(2,2,5,6)
const infateResult = testControl.inflateRectBy(refRect, 2)
const inflatedBy2Rect = Qt.rect(-0.5, -1, 10, 12)
compare(infateResult, inflatedBy2Rect, "Inflating rectangle failed")
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
}
function test_inflateRectBy_deflateByHalf() {
const refRect = Qt.rect(3,4,2,3)
const inflateResult = testControl.inflateRectBy(refRect, 0.5)
const defaltedByHalfRect = Qt.rect(3.5, 4.75, 1, 1.5)
compare(inflateResult, defaltedByHalfRect, "Inflating rectangle failed")
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
}
function test_rectCenter() {
compare(testControl.rectCenter(Qt.rect(2, 3, 2, 4)), Qt.point(3, 5), "Fail to retrieve center");
compare(testControl.rectCenter(Qt.rect(-1, 3, 1, 3)), Qt.point(-0.5, 4.5), "Fail to retrieve center");
}
function test_recenterRect() {
compare(testControl.recenterRect(Qt.rect(1.5, 2, 3, 4), Qt.point(0, 0)), Qt.rect(-1.5, -2, 3, 4))
compare(testControl.recenterRect(Qt.rect(0, 0, 1.5, 3), Qt.point(0.75, 1.5)), Qt.rect(0, 0, 1.5, 3))
}
}
MonitorQtOutput {
id: qtOuput
}
}
@@ -4,6 +4,8 @@ import QtTest 1.0
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.TestHelpers 0.1
Item {
width: 300
height: 100
@@ -28,13 +30,16 @@ Item {
}
TestCase {
id: regexTC
name: "RegexValidationTest"
when: windowShown
//
// Test guards
function initTestCase() {
function init() {
qtOuput.restartCapturing()
mouseClick(statusInput)
}
@@ -50,9 +55,9 @@ Item {
}
function test_regex_validation() {
keyClick(Qt.Key_1)
TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_1)
verify(statusInput.valid, "Expected valid input")
keyClick(Qt.Key_Ampersand)
TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_Ampersand)
verify(!statusInput.valid, "Expected invalid input")
}
@@ -61,10 +66,10 @@ Item {
verify(statusInput.valid, "Expected valid input")
verify(statusInput.text.length === 0, "Expected no input")
keyClick(Qt.Key_2)
TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_2)
verify(statusInput.valid, "Expected valid input")
verify(statusInput.text === "2", "Expect one character")
keyClick(Qt.Key_Ampersand)
TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_Ampersand)
verify(statusInput.valid, "Expected invalid input")
verify(statusInput.text === "2", "Expect the same input")
}
@@ -74,12 +79,43 @@ Item {
const appendInvalidChars = "#@!*"
statusInput.text = "invalid $" + appendInvalidChars
keyClick(Qt.Key_End)
TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_End)
verify(!statusInput.valid, "Expected invalid input due to characters not matching")
// Delete invalid characters to get a valid text
for(let i = 0; i < appendInvalidChars.length; ++i)
keyClick(Qt.Key_Backspace)
TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_Backspace)
verify(statusInput.valid, "Expected valid input")
}
}
TestCase {
id: qmlWarnTC
name: "StatusInput"
when: windowShown
//
// Test guards
function initTestCase() {
}
function cleanup() {
statusInput.text = ""
statusInput.validationMode = _defaultValidationMode
}
//
// Tests
function test_initial_empty_is_valid() {
mouseClick(statusInput)
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
}
}
MonitorQtOutput {
id: qtOuput
}
}
@@ -0,0 +1 @@
---

Some files were not shown because too many files have changed in this diff Show More