Compare commits
15 Commits
23407ea36b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 63e360d427 | |||
| 7a324e6119 | |||
| 8abc848325 | |||
| 8c7ecc1b14 | |||
| 82d045d4df | |||
| 1b60860354 | |||
| 33cfb4b539 | |||
| 07ec781b68 | |||
| 2b49211315 | |||
| 26767ae588 | |||
| 2669f7e144 | |||
| 175cea641b | |||
| 10ff3c170e | |||
| be6647e6d6 | |||
| 603da98035 |
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1 +1,13 @@
|
||||
.session
|
||||
|
||||
# Devenv
|
||||
.devenv*
|
||||
devenv.local.nix
|
||||
devenv.local.yaml
|
||||
|
||||
# direnv
|
||||
.direnv
|
||||
|
||||
# pre-commit
|
||||
.pre-commit-config.yaml
|
||||
/.qmlls.ini
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/run/user/1000/quickshell/vfs/feb6ddef68a55c1886b9c19683c71793/.qmlls.ini
|
||||
48
Bar.qml
48
Bar.qml
@@ -1,8 +1,9 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import "./Services/"
|
||||
import "Bar"
|
||||
import "Color.js" as Colors
|
||||
|
||||
@@ -19,6 +20,15 @@ Scope {
|
||||
required property var modelData
|
||||
property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
|
||||
|
||||
function refocus() {
|
||||
if (WlrLayershell == null)
|
||||
return;
|
||||
WlrLayershell.keyboardFocus = WlrKeyboardFocus.None;
|
||||
Qt.callLater(() => {
|
||||
WlrLayershell.keyboardFocus = WlrKeyboardFocus.Exclusive;
|
||||
});
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
exclusionMode: ExclusionMode.Normal
|
||||
exclusiveZone: barExclusionZone
|
||||
@@ -39,6 +49,26 @@ Scope {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (this.WlrLayershell != null) {
|
||||
this.WlrLayershell.keyboardFocus = WlrKeyboardFocus.OnDemand;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
function onRaiseCancelled() {
|
||||
win.WlrLayershell.keyboardFocus = WlrKeyboardFocus.OnDemand;
|
||||
}
|
||||
|
||||
function onRaiseRequested() {
|
||||
// win.WlrLayershell.keyboardFocus = WlrKeyboardFocus.Exclusive;
|
||||
win.refocus();
|
||||
}
|
||||
|
||||
target: FocusManager
|
||||
}
|
||||
|
||||
anchors {
|
||||
left: true
|
||||
right: true
|
||||
@@ -57,7 +87,12 @@ Scope {
|
||||
height: childrenRect.height
|
||||
|
||||
anchors {
|
||||
right: parent.right
|
||||
left: parent.left
|
||||
leftMargin: 7
|
||||
}
|
||||
|
||||
Left {
|
||||
monitor: win.monitor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +116,12 @@ Scope {
|
||||
height: childrenRect.height
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
rightMargin: 7
|
||||
}
|
||||
|
||||
Right {
|
||||
monitor: win.monitor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
296
Bar/Center.qml
296
Bar/Center.qml
@@ -1,39 +1,46 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Services.Notifications
|
||||
import "../Color.js" as Colors
|
||||
import "../Components/"
|
||||
import "../Services/"
|
||||
import "./Center"
|
||||
|
||||
Container {
|
||||
id: centerPill
|
||||
id: root
|
||||
|
||||
property Notification latestNotification: NotificationManager.getLatestNotification() || null
|
||||
required property HyprlandMonitor monitor
|
||||
property NotificationServer notifServer: NotificationManager.notif
|
||||
property string previousState
|
||||
|
||||
boxColor: Colors.mauve
|
||||
defaultItem: time
|
||||
exclusiveMonitor: centerPill.monitor
|
||||
exclusiveMonitor: root.monitor
|
||||
exclusiveToScreen: true
|
||||
state: ""
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
|
||||
PropertyChanges {
|
||||
centerPill.boxHeight: 28
|
||||
centerPill.boxRadius: 9
|
||||
centerPill.boxWidth: 100
|
||||
root.boxHeight: 28
|
||||
root.boxRadius: 9
|
||||
root.boxWidth: 100
|
||||
root.visibleTopMargin: 0
|
||||
}
|
||||
|
||||
StateChangeScript {
|
||||
script: {
|
||||
if (centerPill.previousState != "hovered") {
|
||||
centerPill.stack.replace(time);
|
||||
if (root.previousState != "hovered") {
|
||||
root.stack.replace(time);
|
||||
}
|
||||
centerPill.previousState = "";
|
||||
root.previousState = "";
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -41,14 +48,32 @@ Container {
|
||||
name: "hovered"
|
||||
|
||||
PropertyChanges {
|
||||
centerPill.boxHeight: 35
|
||||
centerPill.boxRadius: 15
|
||||
centerPill.boxWidth: 110
|
||||
root.boxHeight: 35
|
||||
root.boxRadius: 15
|
||||
root.boxWidth: 110
|
||||
root.visibleTopMargin: 0
|
||||
}
|
||||
|
||||
StateChangeScript {
|
||||
script: {
|
||||
centerPill.previousState = "hovered";
|
||||
root.previousState = "hovered";
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "mprisToast"
|
||||
|
||||
PropertyChanges {
|
||||
root.boxHeight: 35
|
||||
root.boxRadius: 15
|
||||
root.boxWidth: 350
|
||||
root.visibleTopMargin: 0
|
||||
}
|
||||
|
||||
StateChangeScript {
|
||||
script: {
|
||||
root.previousState = "mpris";
|
||||
root.stack.replace(mpris);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -56,28 +81,157 @@ Container {
|
||||
name: "expanded"
|
||||
|
||||
PropertyChanges {
|
||||
centerPill.boxHeight: 140
|
||||
centerPill.boxRadius: 30
|
||||
centerPill.boxWidth: 300
|
||||
root.boxHeight: 140
|
||||
root.boxRadius: 30
|
||||
root.boxWidth: 300
|
||||
root.visibleTopMargin: 0
|
||||
}
|
||||
|
||||
StateChangeScript {
|
||||
script: {
|
||||
centerPill.stack.replace(fullTime);
|
||||
centerPill.previousState = "expanded";
|
||||
if (root.state != "notifications") {
|
||||
root.stack.replace(fullTime);
|
||||
root.previousState = "expanded";
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
extend: "expanded"
|
||||
name: "notifications"
|
||||
|
||||
PropertyChanges {
|
||||
root.boxHeight: 320
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "notified"
|
||||
|
||||
PropertyChanges {
|
||||
root.boxHeight: 90
|
||||
root.boxRadius: 30
|
||||
root.boxWidth: 330
|
||||
root.visibleTopMargin: 20
|
||||
}
|
||||
|
||||
StateChangeScript {
|
||||
script: {
|
||||
root.stack.replace(notification);
|
||||
root.previousState = "notified";
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "logout"
|
||||
|
||||
PropertyChanges {
|
||||
root.boxHeight: 90
|
||||
root.boxRadius: 30
|
||||
root.boxWidth: 330
|
||||
root.visibleTopMargin: 20
|
||||
}
|
||||
|
||||
StateChangeScript {
|
||||
script: {
|
||||
root.stack.replace(logout);
|
||||
root.previousState = "logout";
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
mouse.onClicked: {
|
||||
centerPill.state == "expanded" ? centerPill.state = "" : centerPill.state = "expanded";
|
||||
hover.onHoveredChanged: {
|
||||
if (hover.hovered == true) {
|
||||
if (root.state == "notified") {
|
||||
root.state = "notified";
|
||||
notificationViewTimer.stop();
|
||||
} else if (root.state == "logout") {
|
||||
root.state = "logout";
|
||||
} else if (MprisManager.getPlaying() == true) {
|
||||
root.state = "mprisToast";
|
||||
} else {
|
||||
root.state = "hovered";
|
||||
}
|
||||
} else {
|
||||
root.state = "";
|
||||
FocusManager.exitRaise();
|
||||
}
|
||||
}
|
||||
mouse.onEntered: {
|
||||
centerPill.state = "hovered";
|
||||
onLatestNotificationChanged: {
|
||||
if (latestNotification != null && Hyprland.focusedMonitor == monitor) {
|
||||
if (root.latestNotification.lastGeneration == false) {
|
||||
root.state = "notified";
|
||||
notificationViewTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
mouse.onExited: {
|
||||
centerPill.state = "";
|
||||
tap.onTapped: {
|
||||
root.state == "hovered" ? root.state = "expanded" : undefined;
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onactivated() {
|
||||
console.log("some", root.state);
|
||||
}
|
||||
|
||||
target: root.stack
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onLogoutTriggered() {
|
||||
if (Hyprland.focusedMonitor == root.exclusiveMonitor) {
|
||||
root.state = "logout";
|
||||
}
|
||||
}
|
||||
|
||||
target: IpcManager
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onRaiseCancelled() {
|
||||
root.state = "";
|
||||
}
|
||||
|
||||
target: FocusManager
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onTrackChanged() {
|
||||
if (Hyprland.focusedMonitor == root.monitor) {
|
||||
musicToastTimer.restart();
|
||||
if (root.state == "") {
|
||||
root.state = "mprisToast";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target: MprisManager.defaultPlayer
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: notificationViewTimer
|
||||
|
||||
interval: 3000
|
||||
repeat: false
|
||||
running: false
|
||||
|
||||
onTriggered: {
|
||||
root.state = "";
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: musicToastTimer
|
||||
|
||||
interval: 3000
|
||||
repeat: false
|
||||
running: false
|
||||
|
||||
onTriggered: {
|
||||
if (!root.hover.hovered) {
|
||||
root.state = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -91,52 +245,72 @@ Container {
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fullTime
|
||||
id: mpris
|
||||
|
||||
Item {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 30
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
color: "transparent"
|
||||
position: 0.0
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
color: "transparent"
|
||||
position: 0.6
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
color: Colors.red
|
||||
position: 1.0
|
||||
}
|
||||
}
|
||||
Mpris {
|
||||
TapHandler {
|
||||
onTapped: root.state = "expanded"
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
font.pointSize: 30
|
||||
text: Time.time
|
||||
}
|
||||
onTapped: MprisManager.skip()
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Colors.surface0
|
||||
text: Time.date
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.MiddleButton
|
||||
|
||||
font {
|
||||
pointSize: 9
|
||||
weight: 400
|
||||
onTapped: MprisManager.prev()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fullTime
|
||||
|
||||
Expanded {
|
||||
NotificationList {
|
||||
id: notifList
|
||||
|
||||
animEnabled: false
|
||||
radius: root.boxRadius
|
||||
rootState: root.state
|
||||
server: root.notifServer
|
||||
state: ""
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
notifList.animEnabled = true;
|
||||
root.state = "notifications";
|
||||
Qt.callLater(() => {
|
||||
notifList.animEnabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: notification
|
||||
|
||||
NotificationDisplay {
|
||||
body: root.latestNotification.body
|
||||
summary: root.latestNotification.summary
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
root.state = "notifications";
|
||||
root.stack.replace(fullTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: logout
|
||||
|
||||
Logout {}
|
||||
}
|
||||
}
|
||||
|
||||
55
Bar/Center/Expanded.qml
Normal file
55
Bar/Center/Expanded.qml
Normal file
@@ -0,0 +1,55 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import "../../Color.js" as Colors
|
||||
import "../../Components/"
|
||||
import "../../Services/"
|
||||
|
||||
Item {
|
||||
id: fullTimeRoot
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 30
|
||||
|
||||
gradient: MidpointGradient {
|
||||
color: Colors.red
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: timeColumnContainer
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Column {
|
||||
id: timeColumn
|
||||
|
||||
spacing: 5
|
||||
|
||||
anchors {
|
||||
centerIn: parent
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Colors.surface0
|
||||
text: Time.time
|
||||
|
||||
font {
|
||||
pointSize: 30
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Colors.surface1
|
||||
text: Time.date
|
||||
|
||||
font {
|
||||
pointSize: 9
|
||||
weight: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Bar/Center/Logout.qml
Normal file
18
Bar/Center/Logout.qml
Normal file
@@ -0,0 +1,18 @@
|
||||
import QtQuick
|
||||
import "../../Components/"
|
||||
import "../../Services/"
|
||||
|
||||
Item {
|
||||
CenteredText {
|
||||
text: "some"
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Escape"
|
||||
|
||||
onActivated: {
|
||||
console.log("shortcut triggered");
|
||||
FocusManager.exitRaise();
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Bar/Center/Mpris.qml
Normal file
62
Bar/Center/Mpris.qml
Normal file
@@ -0,0 +1,62 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Mpris
|
||||
import "../../Color.js" as Colors
|
||||
import "../../Components/"
|
||||
import "../../Services/"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property MprisPlayer player: MprisManager.defaultPlayer
|
||||
|
||||
Rectangle {
|
||||
color: Colors.red
|
||||
radius: 5
|
||||
|
||||
anchors {
|
||||
bottomMargin: 15
|
||||
fill: parent
|
||||
leftMargin: 10
|
||||
rightMargin: parent.width - ((parent.width * MprisManager.pos) - 10)
|
||||
topMargin: 15
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors {
|
||||
bottomMargin: 2
|
||||
fill: parent
|
||||
leftMargin: 7
|
||||
rightMargin: 7
|
||||
topMargin: 2
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: MprisManager.defaultPlayer.trackArtUrl
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
StyledText {
|
||||
elide: Qt.ElideRight
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
text: MprisManager.defaultPlayer.trackArtist + " - " + MprisManager.defaultPlayer.trackTitle
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Bar/Center/NotificationDisplay.qml
Normal file
49
Bar/Center/NotificationDisplay.qml
Normal file
@@ -0,0 +1,49 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import "../../Color.js" as Colors
|
||||
import "../../Components/"
|
||||
import "../../Services/"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property string body
|
||||
required property string summary
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 30
|
||||
|
||||
gradient: MidpointGradient {
|
||||
color: Colors.green
|
||||
midpoint: 0.7
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
font.pointSize: 11
|
||||
text: root.summary
|
||||
}
|
||||
|
||||
StyledText {
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
maximumLineCount: 2
|
||||
text: root.body
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
width: root.width - 20
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
font {
|
||||
pointSize: 10
|
||||
weight: 500
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
307
Bar/Center/NotificationList.qml
Normal file
307
Bar/Center/NotificationList.qml
Normal file
@@ -0,0 +1,307 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import "../../Color.js" as Colors
|
||||
import "../../Components/"
|
||||
import "../../Services/"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property bool animEnabled
|
||||
required property double radius
|
||||
required property string rootState
|
||||
required property NotificationServer server
|
||||
|
||||
Behavior on anchors.topMargin {
|
||||
enabled: root.animEnabled
|
||||
|
||||
SpringAnimation {
|
||||
damping: 0.35
|
||||
spring: 4
|
||||
}
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
when: root.rootState != "notifications"
|
||||
|
||||
PropertyChanges {
|
||||
root.anchors.topMargin: root.parent.height - 21
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "expanded"
|
||||
when: root.rootState == "notifications"
|
||||
|
||||
PropertyChanges {
|
||||
root.anchors.topMargin: 5
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
topMargin: parent.height - 21
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
|
||||
anchors.fill: parent
|
||||
color: Colors.surface0
|
||||
radius: root.radius - 3
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
when: root.rootState != "notifications"
|
||||
|
||||
PropertyChanges {}
|
||||
},
|
||||
State {
|
||||
name: "expanded"
|
||||
when: root.rootState == "notifications"
|
||||
|
||||
PropertyChanges {
|
||||
background.anchors.bottomMargin: 5
|
||||
background.anchors.leftMargin: 5
|
||||
background.anchors.rightMargin: 5
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
anchors {
|
||||
bottomMargin: 15
|
||||
leftMargin: 20
|
||||
rightMargin: 20
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: notifListContainer
|
||||
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
opacity: 0
|
||||
|
||||
anchors {
|
||||
bottomMargin: 5
|
||||
leftMargin: 5
|
||||
rightMargin: 5
|
||||
topMargin: 5
|
||||
}
|
||||
|
||||
CenteredText {
|
||||
color: Colors.overlay0
|
||||
text: "No Notifications"
|
||||
visible: root.server.trackedNotifications.values.length <= 0
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: notifScrollingList
|
||||
|
||||
anchors.fill: parent
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
height: Math.min(contentHeight, parent.height)
|
||||
implicitHeight: background.height
|
||||
model: root.server.trackedNotifications.values.length
|
||||
spacing: 7
|
||||
|
||||
delegate: Item {
|
||||
id: notifRoot
|
||||
|
||||
property Notification currentNotif: root.server.trackedNotifications.values[(notifRoot.notifLen
|
||||
- 1) - index]
|
||||
required property real index
|
||||
property real notifLen: root.server.trackedNotifications.values.length
|
||||
|
||||
height: 50
|
||||
width: ListView.view.width
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
when: notifHover.hovered == false
|
||||
},
|
||||
State {
|
||||
name: "hovered"
|
||||
when: notifHover.hovered == false
|
||||
|
||||
PropertyChanges {}
|
||||
}
|
||||
]
|
||||
|
||||
HoverHandler {
|
||||
id: notifHover
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dismissButton
|
||||
|
||||
color: Colors.red
|
||||
implicitWidth: 100
|
||||
radius: 10
|
||||
|
||||
Behavior on anchors.rightMargin {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
when: notifHover.hovered == false
|
||||
},
|
||||
State {
|
||||
name: "hovered"
|
||||
when: notifHover.hovered == true
|
||||
|
||||
PropertyChanges {
|
||||
dismissButton.anchors.rightMargin: 5
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
StyledText {
|
||||
color: Colors.surface0
|
||||
font.pointSize: 15
|
||||
horizontalAlignment: Qt.AlignRight
|
||||
text: ""
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
rightMargin: 14
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
notifRoot.currentNotif.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 3
|
||||
right: parent.right
|
||||
rightMargin: 10
|
||||
top: parent.top
|
||||
topMargin: 3
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: textContainer
|
||||
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
color: Colors.surface2
|
||||
radius: 9
|
||||
|
||||
Behavior on anchors.rightMargin {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
when: notifHover.hovered == false
|
||||
},
|
||||
State {
|
||||
name: "hovered"
|
||||
when: notifHover.hovered == true
|
||||
|
||||
PropertyChanges {
|
||||
textContainer.anchors.rightMargin: 40
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Column {
|
||||
id: textColumn
|
||||
|
||||
anchors {
|
||||
centerIn: parent
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
StyledText {
|
||||
color: Colors.text
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: notifRoot.currentNotif.summary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
width: notifRoot.width - 10
|
||||
}
|
||||
|
||||
StyledText {
|
||||
color: Colors.subtext1
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: notifRoot.currentNotif.body
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
width: notifRoot.width - 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anchors {
|
||||
bottomMargin: 5
|
||||
leftMargin: 10
|
||||
rightMargin: 10
|
||||
topMargin: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
id: notifListClipper
|
||||
|
||||
anchors.fill: notifListContainer
|
||||
maskEnabled: true
|
||||
maskSource: clipsource
|
||||
opacity: 0
|
||||
source: notifListContainer
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
when: root.rootState != "notifications"
|
||||
|
||||
PropertyChanges {
|
||||
notifListClipper.opacity: 0
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "expanded"
|
||||
when: root.rootState == "notifications"
|
||||
|
||||
PropertyChanges {
|
||||
notifListClipper.opacity: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clipsource
|
||||
|
||||
anchors.fill: background
|
||||
color: "black"
|
||||
layer.enabled: true
|
||||
radius: background.radius
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
70
Bar/Left.qml
Normal file
70
Bar/Left.qml
Normal file
@@ -0,0 +1,70 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.Notifications
|
||||
import "../Color.js" as Colors
|
||||
import "../Components/"
|
||||
import "../Services/"
|
||||
import "./Center"
|
||||
import "./Left/"
|
||||
|
||||
Container {
|
||||
id: root
|
||||
|
||||
property double extraHoveredWidth: 0
|
||||
required property HyprlandMonitor monitor
|
||||
property double rowLeftMargin: 4
|
||||
property double rowSpacing: 3
|
||||
property double rowYmargin: 3
|
||||
property double selectorFocusedExtra: 15
|
||||
property double selectorHeight: 20
|
||||
property double selectorWidth: 20
|
||||
property double spacing: 3
|
||||
property double workspaceNumber: WorkspaceManager.getNumberOfWorkspaces(monitor)
|
||||
|
||||
boxHeight: 25
|
||||
boxWidth: (selectorWidth * workspaceNumber) + ((2 * rowLeftMargin) + (rowSpacing * (
|
||||
workspaceNumber - 1))) + extraHoveredWidth + (hoverHandler.hovered == true
|
||||
? selectorFocusedExtra : 0)
|
||||
exclusiveMonitor: monitor
|
||||
|
||||
defaultItem: WorkspaceContainer {
|
||||
extraHoveredWidth: root.extraHoveredWidth
|
||||
monitor: root.monitor
|
||||
rowLeftMargin: root.rowLeftMargin
|
||||
rowSpacing: root.rowSpacing
|
||||
rowYmargin: root.rowYmargin
|
||||
selectorFocusedExtra: root.selectorFocusedExtra
|
||||
selectorHeight: root.selectorHeight
|
||||
selectorWidth: root.selectorWidth
|
||||
}
|
||||
Behavior on rowYmargin {
|
||||
SpringAnimation {
|
||||
damping: 0.3
|
||||
spring: 4
|
||||
}
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
when: !hoverHandler.hovered
|
||||
},
|
||||
State {
|
||||
name: "hovered"
|
||||
when: hoverHandler.hovered
|
||||
|
||||
PropertyChanges {
|
||||
root.boxHeight: 40
|
||||
root.extraHoveredWidth: 25
|
||||
root.rowYmargin: 5
|
||||
root.selectorWidth: 40
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
}
|
||||
}
|
||||
86
Bar/Left/WorkspaceContainer.qml
Normal file
86
Bar/Left/WorkspaceContainer.qml
Normal file
@@ -0,0 +1,86 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import "../../Color.js" as Colors
|
||||
import "../../Components/"
|
||||
import "../../Services/"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property double extraHoveredWidth
|
||||
required property HyprlandMonitor monitor
|
||||
required property double rowLeftMargin
|
||||
required property double rowSpacing
|
||||
required property double rowYmargin
|
||||
required property double selectorFocusedExtra
|
||||
required property double selectorHeight
|
||||
required property double selectorWidth
|
||||
|
||||
Item {
|
||||
id: newWorkspaceButton
|
||||
|
||||
implicitWidth: 20
|
||||
opacity: root.extraHoveredWidth > 0 ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
id: tapHandler
|
||||
|
||||
onTapped: WorkspaceManager.activateNextFreeWorkspace()
|
||||
}
|
||||
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 5 + root.rowYmargin
|
||||
right: parent.right
|
||||
rightMargin: 4
|
||||
top: parent.top
|
||||
topMargin: 5 + root.rowYmargin
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Colors.green
|
||||
radius: 4
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: "+"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: root.rowSpacing
|
||||
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
bottomMargin: root.rowYmargin
|
||||
left: parent.left
|
||||
leftMargin: root.rowLeftMargin
|
||||
top: parent.top
|
||||
topMargin: root.rowYmargin
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: WorkspaceManager.getWorkspacesForMonitor(root.monitor)
|
||||
|
||||
WorkspaceSelector {
|
||||
required property HyprlandWorkspace modelData
|
||||
|
||||
extraHoveredWidth: root.extraHoveredWidth
|
||||
selectorFocusedExtra: root.selectorFocusedExtra
|
||||
selectorHeight: root.selectorHeight
|
||||
selectorWidth: root.selectorWidth
|
||||
workspace: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
87
Bar/Left/WorkspaceSelector.qml
Normal file
87
Bar/Left/WorkspaceSelector.qml
Normal file
@@ -0,0 +1,87 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import "../../Color.js" as Colors
|
||||
import "../../Components/"
|
||||
import "../../Services/"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property double extraHoveredWidth
|
||||
property bool hovered: extraHoveredWidth > 0 ? true : false
|
||||
property color selectorColor: Colors.surface2
|
||||
required property double selectorFocusedExtra
|
||||
required property double selectorHeight
|
||||
required property double selectorWidth
|
||||
required property HyprlandWorkspace workspace
|
||||
property string workspaceState: WorkspaceManager.getWorkspaceState(workspace)
|
||||
|
||||
implicitWidth: selectorWidth + (root.hovered == true ? (root.workspaceState == "focused"
|
||||
? selectorFocusedExtra : 0) : 0)
|
||||
|
||||
Behavior on implicitWidth {
|
||||
SpringAnimation {
|
||||
damping: 0.3
|
||||
spring: 4
|
||||
}
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
when: root.workspaceState == "inactive"
|
||||
|
||||
PropertyChanges {
|
||||
root.selectorColor: Colors.surface2
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "active"
|
||||
when: root.workspaceState == "active"
|
||||
|
||||
PropertyChanges {
|
||||
root.selectorColor: Colors.overlay2
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "focused"
|
||||
when: root.workspaceState == "focused"
|
||||
|
||||
PropertyChanges {
|
||||
root.selectorColor: Colors.lavender
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
TapHandler {
|
||||
id: tapHandler
|
||||
|
||||
onTapped: root.workspace.activate()
|
||||
}
|
||||
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
top: parent.top
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: root.selectorColor
|
||||
radius: 6
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
}
|
||||
|
||||
CenteredText {
|
||||
font.pointSize: 10.5
|
||||
text: root.workspace.id
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Bar/Right.qml
Normal file
21
Bar/Right.qml
Normal file
@@ -0,0 +1,21 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.Notifications
|
||||
import "../Color.js" as Colors
|
||||
import "../Components/"
|
||||
import "../Services/"
|
||||
import "./Center"
|
||||
import "./Right/"
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
required property HyprlandMonitor monitor
|
||||
|
||||
Bluetooth {
|
||||
exclusiveMonitor: root.monitor
|
||||
}
|
||||
}
|
||||
98
Bar/Right/Bluetooth.qml
Normal file
98
Bar/Right/Bluetooth.qml
Normal file
@@ -0,0 +1,98 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import "../../Color.js" as Colors
|
||||
import "../../Components/"
|
||||
import "../../Services/"
|
||||
import "Bluetooth"
|
||||
|
||||
Container {
|
||||
id: root
|
||||
|
||||
property bool shown: false
|
||||
|
||||
animOffset: 200
|
||||
boxColor: Colors.peach
|
||||
boxHeight: 25
|
||||
boxWidth: 30
|
||||
defaultItem: icon
|
||||
exclusiveToScreen: true
|
||||
forceHidden: !(root.shown || BluetoothManager.getConnected())
|
||||
hoverableWhenHidden: true
|
||||
state: ""
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
|
||||
PropertyChanges {
|
||||
root.boxHeight: 25
|
||||
root.boxWidth: 30
|
||||
}
|
||||
|
||||
StateChangeScript {
|
||||
script: {
|
||||
root.stack.replace(icon);
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "expanded"
|
||||
|
||||
PropertyChanges {
|
||||
root.boxHeight: 250
|
||||
root.boxRadius: 20
|
||||
root.boxWidth: 350
|
||||
}
|
||||
|
||||
StateChangeScript {
|
||||
script: {
|
||||
root.stack.replace(expanded);
|
||||
root.shown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
hover.onHoveredChanged: {
|
||||
if (hover.hovered == false) {
|
||||
root.state = "";
|
||||
hoverIntervalTimer.start();
|
||||
} else {
|
||||
root.shown = true;
|
||||
hoverIntervalTimer.start();
|
||||
}
|
||||
}
|
||||
tap.onTapped: state = "expanded"
|
||||
|
||||
Timer {
|
||||
id: hoverIntervalTimer
|
||||
|
||||
interval: 500
|
||||
repeat: false
|
||||
running: false
|
||||
|
||||
onTriggered: {
|
||||
if (root.hover.hovered == false) {
|
||||
root.shown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: icon
|
||||
|
||||
Item {
|
||||
CenteredText {
|
||||
text: BluetoothManager.getConnected() == true ? "" : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: expanded
|
||||
|
||||
Expanded {}
|
||||
}
|
||||
}
|
||||
384
Bar/Right/Bluetooth/Expanded.qml
Normal file
384
Bar/Right/Bluetooth/Expanded.qml
Normal file
@@ -0,0 +1,384 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Bluetooth
|
||||
import "../../../Color.js" as Colors
|
||||
import "../../../Components/"
|
||||
import "../../../Services/"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property double topBarHeight: 35
|
||||
|
||||
Rectangle {
|
||||
color: Colors.surface0
|
||||
radius: 17
|
||||
|
||||
anchors {
|
||||
bottomMargin: 5
|
||||
fill: parent
|
||||
leftMargin: 5
|
||||
rightMargin: 5
|
||||
topMargin: 5
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: topBar
|
||||
|
||||
property double topBarHeight: root.topBarHeight
|
||||
|
||||
color: Colors.peach
|
||||
implicitHeight: topBarHeight
|
||||
radius: 9
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: 5
|
||||
right: parent.right
|
||||
rightMargin: 5
|
||||
top: parent.top
|
||||
topMargin: 5
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: topBarRow
|
||||
|
||||
property double topBarRowPadding: 4
|
||||
|
||||
spacing: 3
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: topBarRowPadding
|
||||
right: parent.right
|
||||
rightMargin: topBarRowPadding
|
||||
top: parent.top
|
||||
topMargin: topBarRowPadding
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
// color: Qt.lighter(Colors.peach, 1.2)
|
||||
color: Colors.surface1
|
||||
implicitHeight: topBar.topBarHeight - (topBarRow.topBarRowPadding * 2)
|
||||
radius: 6
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Colors.text
|
||||
horizontalAlignment: Qt.AlignLeft
|
||||
text: " " + BluetoothManager.defaultAdapterName
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: scanButton
|
||||
|
||||
color: Qt.lighter(Colors.peach, 1.15)
|
||||
implicitHeight: topBar.topBarHeight - (topBarRow.topBarRowPadding * 2)
|
||||
implicitWidth: topBar.topBarHeight - (topBarRow.topBarRowPadding * 2)
|
||||
radius: 6
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: "hovered"
|
||||
|
||||
PropertyChanges {
|
||||
scanButton.color: Colors.green
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
border {
|
||||
color: Colors.surface0
|
||||
width: 2
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
onHoveredChanged: hovered == true ? scanButton.state = "hovered" : scanButton.state = ""
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
BluetoothManager.toggleDiscover();
|
||||
}
|
||||
}
|
||||
|
||||
CenteredText {
|
||||
id: scanButtonText
|
||||
|
||||
text: ""
|
||||
|
||||
NumberAnimation {
|
||||
id: animateRotation
|
||||
|
||||
duration: 1000
|
||||
from: 0
|
||||
loops: Animation.Infinite
|
||||
properties: "rotation"
|
||||
running: BluetoothManager.defaultAdapter.discovering
|
||||
target: scanButtonText
|
||||
to: 360
|
||||
|
||||
onStopped: {
|
||||
scanButton.rotation = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
clip: true
|
||||
model: BluetoothManager.getDevicesList()
|
||||
spacing: 5
|
||||
|
||||
delegate: Item {
|
||||
id: deviceRoot
|
||||
|
||||
required property real index
|
||||
required property BluetoothDevice modelData
|
||||
|
||||
height: 55
|
||||
width: ListView.view.width
|
||||
|
||||
Rectangle {
|
||||
id: containerBox
|
||||
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
color: Colors.peach
|
||||
radius: 7
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
color: Colors.green
|
||||
implicitHeight: parent.height - 10
|
||||
implicitWidth: parent.width * (deviceRoot.modelData.batteryAvailable == true
|
||||
? deviceRoot.modelData.battery : 0) - 10
|
||||
opacity: 0.7
|
||||
radius: 7
|
||||
visible: deviceRoot.modelData.batteryAvailable == true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors {
|
||||
bottomMargin: 4
|
||||
fill: parent
|
||||
leftMargin: 4
|
||||
rightMargin: 4
|
||||
topMargin: 4
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
color: deviceRoot.modelData.connected ? Colors.green : Qt.lighter(Colors.peach, 1.25)
|
||||
radius: 5
|
||||
|
||||
CenteredText {
|
||||
color: Colors.surface0
|
||||
font.pointSize: 30
|
||||
text: BluetoothManager.getIcon(deviceRoot.modelData.icon)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: Qt.lighter(Colors.peach, 1.15)
|
||||
radius: 4
|
||||
|
||||
StyledText {
|
||||
anchors.fill: parent
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Qt.ElideRight
|
||||
text: deviceRoot.modelData.name
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
|
||||
StyledText {
|
||||
anchors.fill: parent
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Colors.surface2
|
||||
font.weight: 300
|
||||
text: deviceRoot.modelData.address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
id: itemEntry
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: Colors.red
|
||||
radius: 3
|
||||
|
||||
Loader {
|
||||
id: deleteButtonLoader
|
||||
|
||||
anchors.fill: parent
|
||||
sourceComponent: forgetButton
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
when: deviceRoot.modelData.connected == false
|
||||
|
||||
PropertyChanges {
|
||||
deleteButtonLoader.sourceComponent: forgetButton
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "connected"
|
||||
when: deviceRoot.modelData.connected == true
|
||||
|
||||
PropertyChanges {
|
||||
deleteButtonLoader.sourceComponent: disconnectButton
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Component {
|
||||
id: disconnectButton
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
TapHandler {
|
||||
onTapped: deviceRoot.modelData.disconnect()
|
||||
}
|
||||
|
||||
CenteredText {
|
||||
text: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: forgetButton
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
TapHandler {
|
||||
onTapped: deviceRoot.modelData.forget()
|
||||
}
|
||||
|
||||
CenteredText {
|
||||
text: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: greenButton
|
||||
|
||||
property Component loadedItem: connectButton
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: Colors.green
|
||||
radius: 3
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
when: deviceRoot.modelData.paired == true
|
||||
|
||||
PropertyChanges {
|
||||
greenButton.color: Colors.green
|
||||
greenButton.loadedItem: connectButton
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "unpaired"
|
||||
when: deviceRoot.modelData.paired == false
|
||||
|
||||
PropertyChanges {
|
||||
greenButton.color: Colors.blue
|
||||
greenButton.loadedItem: pairButton
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: greenButton.loadedItem
|
||||
}
|
||||
|
||||
Component {
|
||||
id: connectButton
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
CenteredText {
|
||||
text: ""
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: deviceRoot.modelData.connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pairButton
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
CenteredText {
|
||||
text: ""
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
deviceRoot.modelData.pair();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anchors {
|
||||
bottomMargin: 5
|
||||
fill: parent
|
||||
leftMargin: 5
|
||||
rightMargin: 5
|
||||
topMargin: 10 + root.topBarHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,45 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
|
||||
import "../Color.js" as Colors
|
||||
|
||||
Item {
|
||||
id: containerRoot
|
||||
id: root
|
||||
|
||||
property double animOffset: 0
|
||||
property color boxColor: Colors.surface0
|
||||
property double boxHeight: 28
|
||||
property double boxRadius: 9
|
||||
property double boxWidth: 100
|
||||
readonly property double calculatedTopMargin: {
|
||||
if (!root.exclusiveToScreen) {
|
||||
return visibleTopMargin;
|
||||
}
|
||||
|
||||
if (Hyprland.focusedMonitor == root.exclusiveMonitor) {
|
||||
if (!root.forceHidden) {
|
||||
return visibleTopMargin;
|
||||
} else if (root.hoverableWhenHidden) {
|
||||
return 7 - root.boxHeight - 5;
|
||||
}
|
||||
}
|
||||
|
||||
return -4 - root.boxHeight - 5;
|
||||
}
|
||||
property Component defaultItem
|
||||
required property HyprlandMonitor exclusiveMonitor
|
||||
property bool exclusiveToScreen: false
|
||||
property alias mouse: mouseArea
|
||||
property bool forceHidden: false
|
||||
property alias hover: hoverHandler
|
||||
property bool hoverableWhenHidden: false
|
||||
property alias rect: container
|
||||
property alias stack: containerContent
|
||||
property alias tap: tapHandler
|
||||
property double visibleTopMargin: 0
|
||||
|
||||
function getVisible() {
|
||||
if (containerRoot.exclusiveToScreen) {
|
||||
if (Hyprland.focusedMonitor != containerRoot.exclusiveMonitor) {
|
||||
return -4 - containerRoot.boxHeight - 5;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
clip: true
|
||||
implicitHeight: boxHeight
|
||||
implicitWidth: boxWidth
|
||||
|
||||
@@ -59,30 +67,41 @@ Item {
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: getVisible()
|
||||
topMargin: calculatedTopMargin
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: defaultCurve
|
||||
|
||||
duration: 200
|
||||
duration: 200 + root.animOffset
|
||||
easing: Easing.InOutBack
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
z: 1
|
||||
TapHandler {
|
||||
id: tapHandler
|
||||
}
|
||||
|
||||
RectangularShadow {
|
||||
anchors.fill: container
|
||||
blur: 30
|
||||
color: Colors.mantle
|
||||
offset.x: 7
|
||||
offset.y: 3
|
||||
radius: container.radius
|
||||
spread: 10
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: container
|
||||
|
||||
anchors.fill: parent
|
||||
color: containerRoot.boxColor
|
||||
radius: containerRoot.boxRadius
|
||||
clip: true
|
||||
color: root.boxColor
|
||||
radius: root.boxRadius
|
||||
|
||||
StackView {
|
||||
id: containerContent
|
||||
@@ -113,7 +132,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: containerContent.push(containerRoot.defaultItem)
|
||||
Component.onCompleted: containerContent.push(root.defaultItem)
|
||||
onCurrentItemChanged: {
|
||||
if (currentItem) {
|
||||
// Dynamically center the incoming child to the StackView
|
||||
|
||||
23
Components/MidpointGradient.qml
Normal file
23
Components/MidpointGradient.qml
Normal file
@@ -0,0 +1,23 @@
|
||||
import QtQuick
|
||||
|
||||
Gradient {
|
||||
id: root
|
||||
|
||||
required property color color
|
||||
property double midpoint: 0.6
|
||||
|
||||
GradientStop {
|
||||
color: "transparent"
|
||||
position: 0.0
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
color: "transparent"
|
||||
position: root.midpoint
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
color: root.color
|
||||
position: 1.0
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import "../Color.js" as Colors
|
||||
|
||||
Text {
|
||||
color: Colors.base
|
||||
elide: Text.ElideRight
|
||||
|
||||
font {
|
||||
family: "FiraMono Nerd Font"
|
||||
|
||||
69
Services/BluetoothManager.qml
Normal file
69
Services/BluetoothManager.qml
Normal file
@@ -0,0 +1,69 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property BluetoothAdapter defaultAdapter: Bluetooth.defaultAdapter
|
||||
property string defaultAdapterName: Bluetooth.defaultAdapter.adapterId
|
||||
|
||||
function getConnected() {
|
||||
let deviceList = getDevicesList();
|
||||
for (let device in deviceList) {
|
||||
let currentDevice = deviceList[device];
|
||||
if (currentDevice.connected == true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getDevicesList() {
|
||||
return root.defaultAdapter.devices.values;
|
||||
}
|
||||
|
||||
function getIcon(name) {
|
||||
const icons = {
|
||||
"audio-card": "",
|
||||
"audio-input-microphone": "",
|
||||
"audio-headphones": "",
|
||||
"audio-headset": "",
|
||||
"battery": "",
|
||||
"camera-photo": "",
|
||||
"computer": "",
|
||||
"input-keyboard": "",
|
||||
"input-mouse": "",
|
||||
"input-gaming": "",
|
||||
"phone": ""
|
||||
};
|
||||
|
||||
return icons[name] || "";
|
||||
}
|
||||
|
||||
function pairTrustConnect(device) {
|
||||
device.pair();
|
||||
device.trusted = true;
|
||||
device.connect();
|
||||
}
|
||||
|
||||
function toggleDiscover() {
|
||||
if (defaultAdapter.discovering == true) {
|
||||
defaultAdapter.discovering = false;
|
||||
} else {
|
||||
defaultAdapter.discovering = true;
|
||||
discoveringTimeout.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: discoveringTimeout
|
||||
|
||||
interval: 15000
|
||||
repeat: false
|
||||
running: false
|
||||
|
||||
onTriggered: defaultAdapter.discovering = false
|
||||
}
|
||||
}
|
||||
19
Services/FocusManager.qml
Normal file
19
Services/FocusManager.qml
Normal file
@@ -0,0 +1,19 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
signal raiseCancelled
|
||||
signal raiseRequested
|
||||
|
||||
function exitRaise() {
|
||||
console.log("exiting raise");
|
||||
root.raiseCancelled();
|
||||
}
|
||||
|
||||
function requestRaise() {
|
||||
root.raiseRequested();
|
||||
}
|
||||
}
|
||||
18
Services/IpcManager.qml
Normal file
18
Services/IpcManager.qml
Normal file
@@ -0,0 +1,18 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
signal logoutTriggered()
|
||||
|
||||
IpcHandler {
|
||||
function logout() {
|
||||
root.logoutTriggered();
|
||||
}
|
||||
|
||||
target: "main"
|
||||
}
|
||||
}
|
||||
27
Services/MprisManager.qml
Normal file
27
Services/MprisManager.qml
Normal file
@@ -0,0 +1,27 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Mpris
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property MprisPlayer defaultPlayer: Mpris.players.values[0]
|
||||
property real pos: defaultPlayer.position / defaultPlayer.length
|
||||
|
||||
function getPlaying() {
|
||||
if (!defaultPlayer) {
|
||||
return false;
|
||||
} else {
|
||||
return defaultPlayer.playbackState == MprisPlaybackState.Playing;
|
||||
}
|
||||
}
|
||||
|
||||
function prev() {
|
||||
defaultPlayer.previous();
|
||||
}
|
||||
|
||||
function skip() {
|
||||
defaultPlayer.next();
|
||||
}
|
||||
}
|
||||
32
Services/NotificationManager.qml
Normal file
32
Services/NotificationManager.qml
Normal file
@@ -0,0 +1,32 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property alias notif: server
|
||||
|
||||
function getLatestNotification() {
|
||||
let notificationList = server.trackedNotifications.values;
|
||||
let len = notificationList.length;
|
||||
if (len <= 0) {
|
||||
len = 1;
|
||||
}
|
||||
let latestNotification = notificationList[len - 1];
|
||||
return latestNotification;
|
||||
}
|
||||
|
||||
NotificationServer {
|
||||
id: server
|
||||
|
||||
actionsSupported: true
|
||||
bodyMarkupSupported: true
|
||||
bodySupported: true
|
||||
|
||||
onNotification: notification => {
|
||||
notification.tracked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Services/WorkspaceManager.qml
Normal file
79
Services/WorkspaceManager.qml
Normal file
@@ -0,0 +1,79 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
function activateNextFreeWorkspace() {
|
||||
let nextFree = getNextFreeWorkspace();
|
||||
activateWorkspaceById(nextFree);
|
||||
}
|
||||
|
||||
function activateWorkspaceById(id) {
|
||||
Hyprland.dispatch(`hl.dsp.focus({workspace = ${id}})`);
|
||||
}
|
||||
|
||||
function getAllNumberedWorkspaces() {
|
||||
let allWorkspaces = Hyprland.workspaces.values;
|
||||
let filteredWorkspaces = [];
|
||||
for (let workspace in allWorkspaces) {
|
||||
let currentWorkspace = allWorkspaces[workspace];
|
||||
if (!currentWorkspace.name.includes("special")) {
|
||||
if (currentWorkspace) {
|
||||
filteredWorkspaces.push(currentWorkspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredWorkspaces;
|
||||
}
|
||||
|
||||
function getNextFreeWorkspace() {
|
||||
let workspaceList = getAllNumberedWorkspaces();
|
||||
|
||||
if (workspaceList[workspaceList.length - 1].id == workspaceList.length) {
|
||||
return workspaceList.length + 1;
|
||||
} else {
|
||||
let prevWorkspace = 0;
|
||||
for (let workspace in workspaceList) {
|
||||
let currentWorkspace = workspaceList[workspace];
|
||||
if (currentWorkspace.id != (prevWorkspace + 1)) {
|
||||
return currentWorkspace.id - 1;
|
||||
} else {
|
||||
prevWorkspace = currentWorkspace.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getNumberOfWorkspaces(monitor) {
|
||||
let workspaceList = getWorkspacesForMonitor(monitor);
|
||||
return workspaceList.length;
|
||||
}
|
||||
|
||||
function getWorkspaceState(workspace) {
|
||||
if (workspace.focused == true) {
|
||||
return "focused";
|
||||
} else if (workspace.active == true) {
|
||||
return "active";
|
||||
} else {
|
||||
return "inactive";
|
||||
}
|
||||
}
|
||||
|
||||
function getWorkspacesForMonitor(monitor) {
|
||||
let allWorkspaces = getAllNumberedWorkspaces();
|
||||
let monitorWorkspaces = [];
|
||||
|
||||
for (let workspace in allWorkspaces) {
|
||||
let currentWorkspace = allWorkspaces[workspace];
|
||||
if (currentWorkspace.monitor == monitor) {
|
||||
if (currentWorkspace) {
|
||||
monitorWorkspaces.push(currentWorkspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
return monitorWorkspaces;
|
||||
}
|
||||
}
|
||||
8
Services/qmldir
Normal file
8
Services/qmldir
Normal file
@@ -0,0 +1,8 @@
|
||||
module Services
|
||||
singleton Time 1.0 Time.qml
|
||||
singleton NotificationManager 1.0 NotificationManager.qml
|
||||
singleton WorkspaceManager 1.0 WorkspaceManager.qml
|
||||
singleton BluetoothManager 1.0 BluetoothManager.qml
|
||||
singleton MprisManager 1.0 MprisManager.qml
|
||||
singleton IpcManager 1.0 IpcManager.qml
|
||||
singleton FocusManager 1.0 FocusManager.qml
|
||||
65
devenv.lock
Normal file
65
devenv.lock
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"nodes": {
|
||||
"devenv": {
|
||||
"locked": {
|
||||
"dir": "src/modules",
|
||||
"lastModified": 1782153911,
|
||||
"narHash": "sha256-Z7BG4cSJX1ybJeZztEMV8WlE6GrXJsSd34x9eHvpzU8=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "d894e487bb23ed87e0555d2158710844bde65322",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "src/modules",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"inputs": {
|
||||
"nixpkgs-src": "nixpkgs-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1782132010,
|
||||
"narHash": "sha256-ZnAVHdVrotp80iIMm5CSR1fdxPlw7Uwmwxb+O/wsgZ8=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"rev": "12866ae2dddbc0ab8b329915f8072bb9c75bde89",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "rolling",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1781607440,
|
||||
"narHash": "sha256-rxO+uc/KFbSJp+pgyXRuAX6QlG9hJdnt0BXpEQRXY+U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3e41b24abd260e8f71dbe2f5737d24122f972158",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
17
devenv.nix
Normal file
17
devenv.nix
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
env.DEVSHELL_NAME = " devenv/#fab387| quickshell/green";
|
||||
|
||||
packages = with pkgs; [
|
||||
quickshell
|
||||
kdePackages.qtdeclarative
|
||||
];
|
||||
|
||||
processes = {
|
||||
qs.exec = "quickshell -p .";
|
||||
};
|
||||
}
|
||||
4
devenv.yaml
Normal file
4
devenv.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
|
||||
inputs:
|
||||
nixpkgs:
|
||||
url: github:cachix/devenv-nixpkgs/rolling
|
||||
61
flake.lock
generated
61
flake.lock
generated
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1780243769,
|
||||
"narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
42
flake.nix
42
flake.nix
@@ -1,42 +0,0 @@
|
||||
{
|
||||
description = "A simple Rust development environment";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
utils,
|
||||
}:
|
||||
utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
|
||||
name = "flake";
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
quickshell
|
||||
kdePackages.qtdeclarative
|
||||
];
|
||||
|
||||
# Environment variables
|
||||
shellHook = ''
|
||||
export DEVSHELL_NAME=" flake/#89dceb| quickshell/green"
|
||||
|
||||
# Trigger zsh
|
||||
if [[ -z "$ZSH_VERSION" ]]; then
|
||||
exec zsh
|
||||
fi
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user