chore(StatusStickerButton): refactor to use StatusButton
- make it possible for StatusBaseButton to be icon-only (ie just icon, no text, rounded if needed)
This commit is contained in:
parent
05e5b3dad6
commit
7a5dbbd952
|
@ -177,6 +177,39 @@ Column {
|
|||
loading: true
|
||||
}
|
||||
|
||||
// icon only
|
||||
StatusButton {
|
||||
icon.name: "info"
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
icon.name: "info"
|
||||
enabled: false
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
icon.name: "info"
|
||||
loading: true
|
||||
}
|
||||
|
||||
// icon only + small
|
||||
StatusButton {
|
||||
size: StatusBaseButton.Size.Small
|
||||
icon.name: "info"
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
size: StatusBaseButton.Size.Small
|
||||
icon.name: "info"
|
||||
enabled: false
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
size: StatusBaseButton.Size.Small
|
||||
icon.name: "info"
|
||||
loading: true
|
||||
}
|
||||
|
||||
// Flat + small
|
||||
StatusFlatButton {
|
||||
text: "Button"
|
||||
|
|
|
@ -52,6 +52,7 @@ Button {
|
|||
QtObject {
|
||||
id: d
|
||||
readonly property color textColor: root.enabled || root.loading ? root.textColor : root.disabledTextColor
|
||||
readonly property bool iconOnly: root.display === AbstractButton.IconOnly || root.text === ""
|
||||
}
|
||||
|
||||
font.family: Theme.palette.baseFont.name
|
||||
|
@ -59,12 +60,16 @@ Button {
|
|||
font.pixelSize: size === StatusBaseButton.Size.Large ? 15 : 13
|
||||
|
||||
horizontalPadding: {
|
||||
if (d.iconOnly)
|
||||
return spacing
|
||||
if (root.icon.name) {
|
||||
return size === StatusBaseButton.Size.Large ? 18 : 16
|
||||
}
|
||||
return size === StatusBaseButton.Size.Large ? 24 : 12
|
||||
}
|
||||
verticalPadding: {
|
||||
if (d.iconOnly)
|
||||
return spacing
|
||||
switch (size) {
|
||||
case StatusBaseButton.Size.Tiny:
|
||||
return 5
|
||||
|
@ -126,15 +131,13 @@ Button {
|
|||
font: root.font
|
||||
text: root.text
|
||||
color: d.textColor
|
||||
visible: text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.textPosition === StatusBaseButton.TextPosition.Left
|
||||
visible: active && root.text !== ""
|
||||
active: root.textPosition === StatusBaseButton.TextPosition.Left && !d.iconOnly
|
||||
visible: active
|
||||
sourceComponent: text
|
||||
}
|
||||
|
||||
|
@ -143,6 +146,7 @@ Button {
|
|||
|
||||
Layout.preferredWidth: active ? root.icon.width : 0
|
||||
Layout.preferredHeight: active ? root.icon.height : 0
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
active: root.icon.name !== ""
|
||||
sourceComponent: root.isRoundIcon ? roundIcon : baseIcon
|
||||
}
|
||||
|
@ -155,8 +159,8 @@ Button {
|
|||
}
|
||||
|
||||
Loader {
|
||||
active: root.textPosition === StatusBaseButton.TextPosition.Right
|
||||
visible: active && root.text !== ""
|
||||
active: root.textPosition === StatusBaseButton.TextPosition.Right && !d.iconOnly
|
||||
visible: active
|
||||
sourceComponent: text
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ Item {
|
|||
}
|
||||
|
||||
property bool highlighted: false
|
||||
property bool enabled: true
|
||||
|
||||
signal clicked(var mouse)
|
||||
|
||||
|
|
|
@ -57,16 +57,14 @@ StatusListView {
|
|||
}
|
||||
},
|
||||
StatusFlatButton {
|
||||
size: StatusBaseButton.Size.Tiny
|
||||
leftPadding: 4
|
||||
rightPadding: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
size: StatusBaseButton.Size.Small
|
||||
icon.name: model.muted ? "notification-muted" : "notification"
|
||||
onClicked: root.setCommunityMutedClicked(model.id, !model.muted)
|
||||
},
|
||||
StatusFlatButton {
|
||||
size: StatusBaseButton.Size.Tiny
|
||||
leftPadding: 4
|
||||
rightPadding: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
size: StatusBaseButton.Size.Small
|
||||
icon.name: "invite-users"
|
||||
onClicked: root.inviteFriends(model)
|
||||
}
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQml 2.15
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
import utils 1.0
|
||||
import shared 1.0
|
||||
import shared.panels 1.0
|
||||
|
||||
Item {
|
||||
StatusButton {
|
||||
id: root
|
||||
|
||||
enum StyleType {
|
||||
Default,
|
||||
LargeNoIcon
|
||||
}
|
||||
|
||||
property alias tooltip: tooltip
|
||||
property int style: StatusStickerButton.StyleType.Default
|
||||
property int packPrice: 0
|
||||
|
@ -25,50 +24,77 @@ Item {
|
|||
property bool hasUpdate: false
|
||||
property bool isTimedOut: false
|
||||
property bool hasInsufficientFunds: false
|
||||
property bool enabled: true
|
||||
property bool greyedOut: false
|
||||
property var icon: new Object({
|
||||
path: Style.svg("status-logo-no-bg"),
|
||||
rotation: 0,
|
||||
runAnimation: false
|
||||
})
|
||||
property string text: root.style === StatusStickerButton.StyleType.Default ? packPrice : qsTr("Buy for %1 SNT").arg(packPrice )
|
||||
property color textColor: root.greyedOut ? Theme.palette.baseColor1 : style === StatusStickerButton.StyleType.Default ? Style.current.roundedButtonSecondaryForegroundColor : Style.current.buttonForegroundColor
|
||||
property color bgColor: root.greyedOut ? Theme.palette.baseColor2 : style === StatusStickerButton.StyleType.Default ? Style.current.blue : Style.current.secondaryBackground
|
||||
|
||||
signal uninstallClicked()
|
||||
signal installClicked()
|
||||
signal cancelClicked()
|
||||
signal updateClicked()
|
||||
signal buyClicked()
|
||||
width: pill.width
|
||||
|
||||
text: root.style === StatusStickerButton.StyleType.Default ? packPrice : qsTr("Buy for %1 SNT").arg(packPrice)
|
||||
icon.name: root.style === StatusStickerButton.StyleType.Default ? d.iconName : ""
|
||||
|
||||
size: root.style === StatusStickerButton.StyleType.LargeNoIcon ? StatusBaseButton.Size.Large : StatusBaseButton.Size.Small
|
||||
radius: root.style === StatusStickerButton.StyleType.LargeNoIcon ? 8 : width/2
|
||||
type: StatusBaseButton.Type.Primary
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property string iconName: "status"
|
||||
}
|
||||
|
||||
Binding on textColor {
|
||||
when: root.greyedOut && !root.isInstalled
|
||||
value: disabledTextColor
|
||||
delayed: true
|
||||
}
|
||||
|
||||
Binding on normalColor {
|
||||
when: root.greyedOut && !root.isInstalled
|
||||
value: disabledColor
|
||||
delayed: true
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "installed"
|
||||
when: root.isInstalled
|
||||
name: "installed_main"
|
||||
when: root.isInstalled && root.style === StatusStickerButton.StyleType.Default
|
||||
PropertyChanges {
|
||||
target: root;
|
||||
text: root.style === StatusStickerButton.StyleType.Default ? "" : qsTr("Uninstall");
|
||||
textColor: root.style === StatusStickerButton.StyleType.Default ? Style.current.roundedButtonSecondaryForegroundColor : root.greyedOut ? Theme.palette.baseColor1 : Style.current.red;
|
||||
bgColor: root.style === StatusStickerButton.StyleType.Default ? Style.current.green : root.greyedOut ? Theme.palette.baseColor2 : Style.current.lightRed;
|
||||
icon: new Object({
|
||||
path: Style.svg("check"),
|
||||
rotation: 0,
|
||||
runAnimation: false
|
||||
})
|
||||
width: 24
|
||||
height: 24
|
||||
icon.width: 12
|
||||
icon.height: 12
|
||||
display: AbstractButton.IconOnly
|
||||
text: ""
|
||||
tooltip.text: qsTr("Uninstall")
|
||||
textColor: Theme.palette.white
|
||||
normalColor: Theme.palette.successColor1
|
||||
hoverColor: Theme.palette.hoverColor(normalColor)
|
||||
}
|
||||
PropertyChanges {
|
||||
target: d
|
||||
iconName: "checkmark"
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "bought"
|
||||
when: root.isBought;
|
||||
name: "installed_popup"
|
||||
when: root.isInstalled && root.style === StatusStickerButton.StyleType.LargeNoIcon
|
||||
PropertyChanges {
|
||||
target: root;
|
||||
text: qsTr("Install");
|
||||
icon: new Object({
|
||||
path: Style.svg("arrowUp"),
|
||||
rotation: 180,
|
||||
runAnimation: false
|
||||
})
|
||||
text: qsTr("Uninstall")
|
||||
tooltip.text: ""
|
||||
type: StatusBaseButton.Type.Danger
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hasUpdate"
|
||||
when: root.hasUpdate
|
||||
extend: "bought"
|
||||
PropertyChanges {
|
||||
target: root;
|
||||
text: qsTr("Update");
|
||||
}
|
||||
},
|
||||
State {
|
||||
|
@ -81,30 +107,15 @@ Item {
|
|||
}
|
||||
},
|
||||
State {
|
||||
name: "insufficientFunds"
|
||||
when: root.hasInsufficientFunds
|
||||
name: "bought"
|
||||
when: root.isBought;
|
||||
PropertyChanges {
|
||||
target: root;
|
||||
text: root.style === StatusStickerButton.StyleType.Default ? packPrice : packPrice + " SNT";
|
||||
textColor: root.style === StatusStickerButton.StyleType.Default ? Style.current.roundedButtonSecondaryForegroundColor : Style.current.darkGrey
|
||||
bgColor: root.style === StatusStickerButton.StyleType.Default ? Style.current.darkGrey : Style.current.buttonDisabledBackgroundColor;
|
||||
enabled: false;
|
||||
text: qsTr("Install");
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "pending"
|
||||
when: root.isPending
|
||||
PropertyChanges {
|
||||
target: root;
|
||||
text: qsTr("Pending...");
|
||||
textColor: root.style === StatusStickerButton.StyleType.Default ? Style.current.roundedButtonSecondaryForegroundColor : Style.current.darkGrey
|
||||
bgColor: root.style === StatusStickerButton.StyleType.Default ? Style.current.darkGrey : Style.current.grey;
|
||||
enabled: false;
|
||||
icon: new Object({
|
||||
path: Style.png("loading"),
|
||||
rotation: 0,
|
||||
runAnimation: true
|
||||
})
|
||||
target: d
|
||||
iconName: "download"
|
||||
}
|
||||
},
|
||||
State {
|
||||
|
@ -114,168 +125,50 @@ Item {
|
|||
PropertyChanges {
|
||||
target: root;
|
||||
text: qsTr("Cancel");
|
||||
textColor: root.style === StatusStickerButton.StyleType.Default ? Style.current.pillButtonTexroundedButtonSecondaryForegroundColortColor : Style.current.red;
|
||||
bgColor: root.style === StatusStickerButton.StyleType.Default ? Style.current.red : Style.current.lightRed;
|
||||
enabled: true
|
||||
type: StatusBaseButton.Type.Danger
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hasUpdate"
|
||||
when: root.hasUpdate
|
||||
extend: "bought"
|
||||
name: "pending"
|
||||
when: root.isPending
|
||||
PropertyChanges {
|
||||
target: root;
|
||||
text: qsTr("Update");
|
||||
text: qsTr("Pending...");
|
||||
enabled: false;
|
||||
loading: true
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "insufficientFunds"
|
||||
when: root.hasInsufficientFunds
|
||||
PropertyChanges {
|
||||
target: root;
|
||||
text: root.style === StatusStickerButton.StyleType.Default ? packPrice : "%1 SNT".arg(packPrice)
|
||||
enabled: false;
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
TextMetrics {
|
||||
id: textMetrics
|
||||
font.weight: Font.Medium
|
||||
font.family: Style.current.baseFont.name
|
||||
font.pixelSize: 15
|
||||
text: root.text
|
||||
// Tooltip only in case we are browsing an item to be installed/downloaded/bought
|
||||
StatusToolTip {
|
||||
id: tooltip
|
||||
visible: root.hovered && text && (root.greyedOut || root.isInstalled)
|
||||
maxWidth: 300
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: pill
|
||||
anchors.right: parent.right
|
||||
width: textMetrics.width + roundedIconImage.width + (Style.current.smallPadding * 2) + 6.7
|
||||
height: 44
|
||||
color: root.bgColor
|
||||
radius: root.style === StatusStickerButton.StyleType.Default ? (width / 2) : 8
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: !root.isPending ? Qt.PointingHandCursor : undefined
|
||||
onClicked: {
|
||||
if (root.isPending || root.greyedOut)
|
||||
return;
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "installed"
|
||||
when: root.isInstalled && root.style === StatusStickerButton.StyleType.Default
|
||||
PropertyChanges {
|
||||
target: pill;
|
||||
width: 28;
|
||||
height: 28
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "large"
|
||||
when: root.style === StatusStickerButton.StyleType.LargeNoIcon
|
||||
PropertyChanges {
|
||||
target: pill;
|
||||
width: textMetrics.width + (Style.current.padding * 4);
|
||||
height: 44
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
SVGImage {
|
||||
id: roundedIconImage
|
||||
width: 12
|
||||
height: 12
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.smallPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: icon.path
|
||||
rotation: icon.rotation
|
||||
RotationAnimator {
|
||||
target: roundedIconImage;
|
||||
from: 0;
|
||||
to: 360;
|
||||
duration: 1200
|
||||
running: visible && root.icon.runAnimation
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: roundedIconImage
|
||||
source: roundedIconImage
|
||||
color: root.greyedOut && !root.isInstalled ? Theme.palette.baseColor1 : Style.current.roundedButtonSecondaryForegroundColor
|
||||
antialiasing: true
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: "installed"
|
||||
when: root.isInstalled && root.style === StatusStickerButton.StyleType.Default
|
||||
PropertyChanges {
|
||||
target: roundedIconImage;
|
||||
anchors.leftMargin: 9
|
||||
width: 11;
|
||||
height: 8
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "large"
|
||||
when: root.style === StatusStickerButton.StyleType.LargeNoIcon
|
||||
PropertyChanges {
|
||||
target: roundedIconImage;
|
||||
visible: false;
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Text {
|
||||
id: content
|
||||
color: root.textColor
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.smallPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.text
|
||||
font.weight: Font.Medium
|
||||
font.family: Style.current.baseFont.name
|
||||
font.pixelSize: 15
|
||||
states: [
|
||||
State {
|
||||
name: "installed"
|
||||
when: root.isInstalled && root.style === StatusStickerButton.StyleType.Default
|
||||
PropertyChanges {
|
||||
target: content;
|
||||
anchors.rightMargin: 9;
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "large"
|
||||
when: root.style === StatusStickerButton.StyleType.LargeNoIcon
|
||||
PropertyChanges {
|
||||
target: content;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
anchors.leftMargin: Style.current.padding * 2
|
||||
anchors.rightMargin: Style.current.padding * 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Tooltip only in case we are browsing an item to be installed/downloaded/bought
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
enabled: root.greyedOut && !root.isInstalled
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
StatusToolTip {
|
||||
id: tooltip
|
||||
visible: hoverHandler.hovered
|
||||
maxWidth: 300
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
enabled: !root.isPending && !root.greyedOut
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.isPending) return;
|
||||
if (root.isInstalled) return root.uninstallClicked();
|
||||
if (root.packPrice === 0 || root.isBought) return root.installClicked()
|
||||
if (root.isTimedOut) return root.cancelClicked()
|
||||
if (root.hasUpdate) return root.updateClicked()
|
||||
return root.buyClicked()
|
||||
}
|
||||
if (root.isInstalled) return root.uninstallClicked();
|
||||
if (root.packPrice === 0 || root.isBought) return root.installClicked()
|
||||
if (root.isTimedOut) return root.cancelClicked()
|
||||
if (root.hasUpdate) return root.updateClicked()
|
||||
return root.buyClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*##^##
|
||||
Designer {
|
||||
D{i:0;autoSize:true;height:480;width:640}
|
||||
}
|
||||
##^##*/
|
||||
|
|
|
@ -133,7 +133,6 @@ Item {
|
|||
|
||||
footer: StatusStickerButton {
|
||||
objectName: "statusStickerMarketInstallButton"
|
||||
height: 44
|
||||
anchors.right: parent.right
|
||||
style: StatusStickerButton.StyleType.LargeNoIcon
|
||||
packPrice: price
|
||||
|
@ -168,8 +167,8 @@ Item {
|
|||
|
||||
StatusStickerButton {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
packPrice: price
|
||||
width: 75 // only needed for Qt Creator
|
||||
isInstalled: installed
|
||||
isBought: bought
|
||||
isPending: pending
|
||||
|
@ -247,22 +246,17 @@ Item {
|
|||
|
||||
Item {
|
||||
id: footer
|
||||
height: 44 - Style.current.padding
|
||||
height: 44
|
||||
anchors.top: availableStickerPacks.bottom
|
||||
|
||||
RoundedIcon {
|
||||
StatusBackButton {
|
||||
id: btnBack
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Style.current.padding / 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.padding / 2
|
||||
width: 28
|
||||
height: 28
|
||||
iconWidth: 17.5
|
||||
iconHeight: 13.5
|
||||
iconColor: Style.current.roundedButtonSecondaryForegroundColor
|
||||
source: Style.svg("arrowUp")
|
||||
rotation: 270
|
||||
width: 24
|
||||
height: 24
|
||||
type: StatusRoundButton.Type.Secondary
|
||||
onClicked: {
|
||||
root.backClicked()
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ ModalPopup {
|
|||
property string thumbnail: ""
|
||||
property string name: ""
|
||||
property string author: ""
|
||||
property int price
|
||||
property string price
|
||||
property bool installed: false;
|
||||
property bool bought: false;
|
||||
property bool pending: false;
|
||||
|
@ -119,7 +119,6 @@ ModalPopup {
|
|||
}
|
||||
|
||||
footer: StatusStickerButton {
|
||||
height: 44
|
||||
anchors.right: parent.right
|
||||
style: StatusStickerButton.StyleType.LargeNoIcon
|
||||
packPrice: price
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import utils 1.0
|
||||
import shared 1.0
|
||||
import shared.panels 1.0
|
||||
|
||||
Item {
|
||||
|
@ -36,14 +37,13 @@ Item {
|
|||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: packIndicator
|
||||
visible: root.selected
|
||||
border.color: Style.current.blue
|
||||
border.width: 1
|
||||
width: parent.width
|
||||
height: 2
|
||||
width: 16
|
||||
x: 4
|
||||
radius: 1
|
||||
color: Theme.palette.primaryColor1
|
||||
y: root.y + root.height + 6
|
||||
}
|
||||
}
|
||||
|
|
|
@ -250,19 +250,18 @@ Popup {
|
|||
RowLayout {
|
||||
id: footerContent
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredHeight: 44
|
||||
Layout.rightMargin: Style.current.padding / 2
|
||||
Layout.leftMargin: Style.current.padding / 2
|
||||
spacing: Style.current.padding / 2
|
||||
|
||||
StatusFlatRoundButton {
|
||||
StatusRoundButton {
|
||||
id: btnAddStickerPack
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
icon.name: "add"
|
||||
type: StatusFlatRoundButton.Type.Tertiary
|
||||
color: "transparent"
|
||||
state: d.stickerPacksLoading ? "default" : "pending"
|
||||
type: StatusFlatRoundButton.Type.Secondary
|
||||
loading: d.stickerPacksLoading
|
||||
onClicked: {
|
||||
stickersContainer.visible = false
|
||||
stickerMarket.visible = true
|
||||
|
@ -285,6 +284,8 @@ Popup {
|
|||
StatusScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
|
||||
|
||||
RowLayout {
|
||||
|
|
Loading…
Reference in New Issue