mirror of
https://github.com/spalencsar/bearwave.git
synced 2026-07-06 22:24:17 +02:00
939 lines
33 KiB
QML
939 lines
33 KiB
QML
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15
|
|
import QtQuick.Layouts 1.15
|
|
import Qt.labs.platform 1.1 as Platform
|
|
|
|
ApplicationWindow {
|
|
id: root
|
|
title: "BearWave"
|
|
width: 980
|
|
height: 700
|
|
minimumWidth: 520
|
|
minimumHeight: 460
|
|
visible: true
|
|
|
|
onClosing: function(close) {
|
|
close.accepted = false
|
|
root.hide()
|
|
}
|
|
|
|
Platform.SystemTrayIcon {
|
|
id: systray
|
|
visible: true
|
|
icon.name: "multimedia-player"
|
|
tooltip: "BearWave"
|
|
|
|
menu: Platform.Menu {
|
|
Platform.MenuItem {
|
|
text: backend && backend.player && backend.player.playing ? "Pause" : "Play"
|
|
onTriggered: {
|
|
if (backend && backend.player) {
|
|
backend.player.togglePlayPause()
|
|
}
|
|
}
|
|
}
|
|
Platform.MenuItem {
|
|
text: root.visible ? "Verstecken" : "Zeigen"
|
|
onTriggered: {
|
|
if (root.visible) {
|
|
root.hide()
|
|
} else {
|
|
root.show()
|
|
root.raise()
|
|
root.requestActivate()
|
|
}
|
|
}
|
|
}
|
|
Platform.MenuSeparator {}
|
|
Platform.MenuItem {
|
|
text: "Beenden"
|
|
onTriggered: Qt.quit()
|
|
}
|
|
}
|
|
|
|
onActivated: function(reason) {
|
|
if (reason === Platform.SystemTrayIcon.Trigger) {
|
|
if (root.visible) {
|
|
root.hide()
|
|
} else {
|
|
root.show()
|
|
root.raise()
|
|
root.requestActivate()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
property var currentPage: "top"
|
|
property var backend: (typeof radioBackend !== "undefined" ? radioBackend : null)
|
|
property bool compactMode: width < 780
|
|
property real contentOpacity: 1.0
|
|
property string activeQuickFilter: ""
|
|
|
|
readonly property color bgA: "#0f141b"
|
|
readonly property color bgB: "#131b25"
|
|
readonly property color panel: "#182433"
|
|
readonly property color card: "#1b2a3d"
|
|
readonly property color cardHover: "#223654"
|
|
readonly property color cardBorder: "#2d4566"
|
|
readonly property color accent: "#2bb0ff"
|
|
readonly property color textMain: "#eaf1fb"
|
|
readonly property color textMuted: "#9eb1c9"
|
|
readonly property color warn: "#ff8b8b"
|
|
|
|
function toast(message) {
|
|
toastLabel.text = message
|
|
toastPopup.open()
|
|
}
|
|
|
|
function activeModel() {
|
|
if (!backend) {
|
|
return []
|
|
}
|
|
return currentPage === "favorites" ? backend.favoriteStations : backend.stations
|
|
}
|
|
|
|
onCurrentPageChanged: {
|
|
contentOpacity = 0.85
|
|
contentFadeRestart.restart()
|
|
}
|
|
|
|
Shortcut {
|
|
sequence: "Space"
|
|
onActivated: {
|
|
if (backend && backend.player) {
|
|
backend.player.togglePlayPause()
|
|
}
|
|
}
|
|
}
|
|
|
|
Shortcut {
|
|
sequence: "Ctrl+F"
|
|
onActivated: searchField.forceActiveFocus()
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
gradient: Gradient {
|
|
GradientStop { position: 0.0; color: bgA }
|
|
GradientStop { position: 1.0; color: bgB }
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: 12
|
|
spacing: 10
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: compactMode ? 208 : 176
|
|
radius: 12
|
|
color: panel
|
|
border.color: cardBorder
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: 10
|
|
spacing: 8
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 8
|
|
|
|
Label {
|
|
text: "BearWave"
|
|
color: textMain
|
|
font.pixelSize: compactMode ? 18 : 22
|
|
font.bold: true
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.preferredWidth: 1
|
|
Layout.fillHeight: true
|
|
color: cardBorder
|
|
opacity: 0.6
|
|
}
|
|
|
|
Button {
|
|
text: "Top"
|
|
highlighted: currentPage === "top"
|
|
onClicked: {
|
|
if (!backend) return
|
|
currentPage = "top"
|
|
activeQuickFilter = ""
|
|
backend.loadTopStations()
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "DE"
|
|
highlighted: currentPage === "german"
|
|
onClicked: {
|
|
if (!backend) return
|
|
currentPage = "german"
|
|
activeQuickFilter = ""
|
|
backend.loadGermanStations()
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "NL"
|
|
highlighted: currentPage === "dutch"
|
|
onClicked: {
|
|
if (!backend) return
|
|
currentPage = "dutch"
|
|
activeQuickFilter = ""
|
|
backend.loadDutchStations()
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "Favoriten"
|
|
highlighted: currentPage === "favorites"
|
|
onClicked: {
|
|
currentPage = "favorites"
|
|
activeQuickFilter = ""
|
|
}
|
|
}
|
|
|
|
Item { Layout.fillWidth: true }
|
|
|
|
Button {
|
|
text: compactMode ? "+" : "Manuell +"
|
|
onClicked: addDialog.open()
|
|
}
|
|
|
|
Button {
|
|
text: "About"
|
|
onClicked: aboutDialog.open()
|
|
}
|
|
|
|
Button {
|
|
visible: backend && backend.canResumeLastStation
|
|
text: compactMode ? "↺" : "Resume"
|
|
onClicked: {
|
|
if (backend) {
|
|
backend.resumeLastStation()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 8
|
|
|
|
TextField {
|
|
id: searchField
|
|
Layout.fillWidth: true
|
|
placeholderText: "Sender suchen (Name, Genre, Land)"
|
|
onTextChanged: {
|
|
if (backend) {
|
|
backend.filterQuery = text
|
|
}
|
|
}
|
|
onAccepted: {
|
|
if (text.length < 2 || !backend) return
|
|
currentPage = "search"
|
|
backend.searchStations(text)
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "Suche"
|
|
highlighted: true
|
|
onClicked: {
|
|
if (searchField.text.length < 2 || !backend) return
|
|
currentPage = "search"
|
|
backend.searchStations(searchField.text)
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: compactMode ? "A-Z" : "Sort A-Z"
|
|
onClicked: {
|
|
if (backend && currentPage !== "favorites") {
|
|
backend.sortStations("name")
|
|
}
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: compactMode ? "kb" : "Sort Bitrate"
|
|
onClicked: {
|
|
if (backend && currentPage !== "favorites") {
|
|
backend.sortStations("bitrate")
|
|
}
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: compactMode ? "❤" : "Sort Votes"
|
|
onClicked: {
|
|
if (backend && currentPage !== "favorites") {
|
|
backend.sortStations("votes")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: "Tipp: Du bist nicht auf DE/NL begrenzt - suche nach Ländern, Genres oder Sendernamen weltweit."
|
|
color: textMuted
|
|
font.pixelSize: 11
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 6
|
|
|
|
Label {
|
|
text: "Genre:"
|
|
color: textMuted
|
|
font.pixelSize: 11
|
|
}
|
|
|
|
Button {
|
|
text: "Rock"
|
|
highlighted: activeQuickFilter === "tag:rock"
|
|
onClicked: {
|
|
if (!backend) return
|
|
currentPage = "genre-rock"
|
|
activeQuickFilter = "tag:rock"
|
|
backend.loadByTag("rock")
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "News"
|
|
highlighted: activeQuickFilter === "tag:news"
|
|
onClicked: {
|
|
if (!backend) return
|
|
currentPage = "genre-news"
|
|
activeQuickFilter = "tag:news"
|
|
backend.loadByTag("news")
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "Jazz"
|
|
highlighted: activeQuickFilter === "tag:jazz"
|
|
onClicked: {
|
|
if (!backend) return
|
|
currentPage = "genre-jazz"
|
|
activeQuickFilter = "tag:jazz"
|
|
backend.loadByTag("jazz")
|
|
}
|
|
}
|
|
|
|
Item { Layout.preferredWidth: 10 }
|
|
|
|
Label {
|
|
text: "Land:"
|
|
color: textMuted
|
|
font.pixelSize: 11
|
|
}
|
|
|
|
Button {
|
|
text: "US"
|
|
highlighted: activeQuickFilter === "cc:US"
|
|
onClicked: {
|
|
if (!backend) return
|
|
currentPage = "country-us"
|
|
activeQuickFilter = "cc:US"
|
|
backend.loadByCountryCode("US")
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "UK"
|
|
highlighted: activeQuickFilter === "cc:GB"
|
|
onClicked: {
|
|
if (!backend) return
|
|
currentPage = "country-gb"
|
|
activeQuickFilter = "cc:GB"
|
|
backend.loadByCountryCode("GB")
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "FR"
|
|
highlighted: activeQuickFilter === "cc:FR"
|
|
onClicked: {
|
|
if (!backend) return
|
|
currentPage = "country-fr"
|
|
activeQuickFilter = "cc:FR"
|
|
backend.loadByCountryCode("FR")
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "WORLD"
|
|
highlighted: activeQuickFilter === "world"
|
|
onClicked: {
|
|
if (!backend) return
|
|
currentPage = "world"
|
|
activeQuickFilter = "world"
|
|
backend.loadWorldStations()
|
|
}
|
|
}
|
|
|
|
Item { Layout.fillWidth: true }
|
|
}
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
visible: backend && backend.lastError.length > 0
|
|
text: backend ? ("Fehler: " + backend.lastError) : ""
|
|
color: warn
|
|
elide: Text.ElideRight
|
|
font.pixelSize: 12
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
radius: 12
|
|
color: panel
|
|
border.color: cardBorder
|
|
clip: true
|
|
opacity: contentOpacity
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
|
|
}
|
|
|
|
ScrollView {
|
|
id: stationScrollView
|
|
anchors.fill: parent
|
|
anchors.margins: 10
|
|
clip: true
|
|
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
|
|
|
ListView {
|
|
id: stationList
|
|
width: stationScrollView.availableWidth
|
|
spacing: 8
|
|
model: activeModel()
|
|
visible: count > 0
|
|
|
|
delegate: Rectangle {
|
|
id: stationCard
|
|
required property int index
|
|
required property var modelData
|
|
width: stationScrollView.availableWidth
|
|
height: compactMode ? 72 : 78
|
|
radius: 10
|
|
color: cardMouse.containsMouse ? cardHover : card
|
|
border.color: cardBorder
|
|
|
|
MouseArea {
|
|
id: cardMouse
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
acceptedButtons: Qt.LeftButton
|
|
onClicked: {
|
|
if (!backend) return
|
|
if (currentPage === "favorites") {
|
|
backend.playFavoriteStation(index)
|
|
} else {
|
|
backend.playStation(index)
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: 10
|
|
spacing: 10
|
|
|
|
Rectangle {
|
|
Layout.preferredWidth: 44
|
|
Layout.preferredHeight: 44
|
|
radius: 8
|
|
color: "#123154"
|
|
border.color: accent
|
|
|
|
Image {
|
|
anchors.fill: parent
|
|
anchors.margins: 3
|
|
source: (stationCard.modelData.favicon && stationCard.modelData.favicon.startsWith("https://"))
|
|
? stationCard.modelData.favicon
|
|
: ""
|
|
fillMode: Image.PreserveAspectFit
|
|
asynchronous: true
|
|
cache: true
|
|
visible: source !== "" && status === Image.Ready
|
|
}
|
|
|
|
Label {
|
|
anchors.centerIn: parent
|
|
text: "FM"
|
|
color: "#d8ecff"
|
|
font.bold: true
|
|
visible: !parent.children[0].visible
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 2
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: stationCard.modelData.name
|
|
color: textMain
|
|
font.bold: true
|
|
font.pixelSize: compactMode ? 13 : 14
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: stationCard.modelData.country + " • "
|
|
+ (stationCard.modelData.bitrate > 0 ? stationCard.modelData.bitrate + " kbps" : "Stream")
|
|
color: textMuted
|
|
font.pixelSize: 11
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
|
|
Button {
|
|
Layout.preferredWidth: compactMode ? 34 : 40
|
|
Layout.preferredHeight: 40
|
|
text: stationCard.modelData.isFavorite ? "★" : "☆"
|
|
onClicked: {
|
|
if (!backend) return
|
|
backend.toggleFavoriteById(stationCard.modelData.uuid, stationCard.modelData.urlResolved)
|
|
toast(stationCard.modelData.isFavorite ? "Aus Favoriten entfernt" : "Zu Favoriten hinzugefügt")
|
|
}
|
|
}
|
|
|
|
Button {
|
|
Layout.preferredWidth: compactMode ? 34 : 40
|
|
Layout.preferredHeight: 40
|
|
text: "▶"
|
|
onClicked: {
|
|
if (!backend) return
|
|
if (currentPage === "favorites") {
|
|
backend.playFavoriteStation(index)
|
|
} else {
|
|
backend.playStation(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Column {
|
|
anchors.centerIn: parent
|
|
spacing: 8
|
|
visible: stationList.count === 0
|
|
|
|
Label {
|
|
text: "Noch keine Sender geladen"
|
|
color: textMain
|
|
font.bold: true
|
|
}
|
|
|
|
Label {
|
|
text: "Lade DE/NL oder nutze die Suche"
|
|
color: textMuted
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: 108
|
|
radius: 12
|
|
color: panel
|
|
border.color: cardBorder
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: 10
|
|
spacing: 12
|
|
|
|
Rectangle {
|
|
Layout.preferredWidth: 88
|
|
Layout.preferredHeight: 88
|
|
radius: 8
|
|
color: "#182637"
|
|
clip: true
|
|
border.color: cardBorder
|
|
|
|
Image {
|
|
id: coverImage
|
|
anchors.fill: parent
|
|
source: backend && backend.player && backend.player.currentCoverArtUrl ? backend.player.currentCoverArtUrl : "qrc:/assets/app/bearwave.png"
|
|
fillMode: backend && backend.player && backend.player.currentCoverArtUrl ? Image.PreserveAspectCrop : Image.PreserveAspectFit
|
|
smooth: true
|
|
asynchronous: true
|
|
anchors.margins: backend && backend.player && backend.player.currentCoverArtUrl ? 0 : 16
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
spacing: 6
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: backend && backend.player ? (backend.player.currentStationName || "Kein Sender ausgewählt") : "Kein Sender ausgewählt"
|
|
color: textMain
|
|
font.pixelSize: 16
|
|
font.bold: true
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: backend && backend.player && backend.player.currentNowPlaying.length > 0
|
|
? ("Jetzt läuft: " + backend.player.currentNowPlaying)
|
|
: "Jetzt läuft: Keine Titelinfo"
|
|
color: textMuted
|
|
font.pixelSize: 12
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 8
|
|
|
|
Button {
|
|
text: "⏮"
|
|
Layout.preferredWidth: 44
|
|
enabled: backend ? backend.hasPreviousStation() : false
|
|
onClicked: {
|
|
if (backend) {
|
|
backend.playPreviousStation()
|
|
}
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: backend && backend.player && backend.player.playing ? "⏸" : "▶"
|
|
Layout.preferredWidth: 44
|
|
onClicked: {
|
|
if (backend && backend.player) {
|
|
backend.player.togglePlayPause()
|
|
}
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "⏹"
|
|
Layout.preferredWidth: 44
|
|
onClicked: {
|
|
if (backend && backend.player) {
|
|
backend.player.stop()
|
|
}
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "⏭"
|
|
Layout.preferredWidth: 44
|
|
enabled: backend ? backend.hasNextStation() : false
|
|
onClicked: {
|
|
if (backend) {
|
|
backend.playNextStation()
|
|
}
|
|
}
|
|
}
|
|
|
|
Label {
|
|
text: "Lautstärke"
|
|
color: textMuted
|
|
}
|
|
|
|
Slider {
|
|
Layout.fillWidth: true
|
|
from: 0
|
|
to: 1
|
|
value: backend && backend.player ? backend.player.volume : 0.5
|
|
onMoved: {
|
|
if (backend && backend.player) {
|
|
backend.player.setVolume(value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: backend && backend.loading
|
|
anchors.fill: parent
|
|
color: "#7f0b121a"
|
|
z: 20
|
|
|
|
Rectangle {
|
|
anchors.centerIn: parent
|
|
width: 210
|
|
height: 110
|
|
radius: 12
|
|
color: panel
|
|
border.color: cardBorder
|
|
|
|
Column {
|
|
anchors.centerIn: parent
|
|
spacing: 10
|
|
|
|
BusyIndicator {
|
|
running: true
|
|
width: 36
|
|
height: 36
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
}
|
|
|
|
Label {
|
|
text: "Lade Sender..."
|
|
color: textMain
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
if (backend) {
|
|
backend.loadTopStations()
|
|
}
|
|
}
|
|
|
|
Dialog {
|
|
id: addDialog
|
|
title: "Sender manuell hinzufügen"
|
|
modal: true
|
|
anchors.centerIn: parent
|
|
width: compactMode ? 320 : 420
|
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
|
|
|
contentItem: ColumnLayout {
|
|
spacing: 8
|
|
TextField { id: manualName; Layout.fillWidth: true; placeholderText: "Name" }
|
|
TextField { id: manualUrl; Layout.fillWidth: true; placeholderText: "Stream URL (http/https)" }
|
|
TextField { id: manualCountry; Layout.fillWidth: true; placeholderText: "Land (optional)" }
|
|
}
|
|
|
|
onAccepted: {
|
|
if (backend) {
|
|
backend.addManualStation(manualName.text, manualUrl.text, manualCountry.text)
|
|
toast("Sender hinzugefügt")
|
|
}
|
|
manualName.text = ""
|
|
manualUrl.text = ""
|
|
manualCountry.text = ""
|
|
}
|
|
}
|
|
|
|
Dialog {
|
|
id: aboutDialog
|
|
modal: true
|
|
anchors.centerIn: Overlay.overlay
|
|
width: compactMode ? 360 : 520
|
|
height: compactMode ? 560 : 680
|
|
standardButtons: Dialog.Ok
|
|
|
|
contentItem: ColumnLayout {
|
|
spacing: 16
|
|
|
|
// --- HEADER ---
|
|
ColumnLayout {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
spacing: 6
|
|
|
|
Image {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: 72
|
|
Layout.preferredHeight: 72
|
|
sourceSize.width: 72
|
|
sourceSize.height: 72
|
|
source: "qrc:/assets/app/bearwave.png"
|
|
fillMode: Image.PreserveAspectFit
|
|
smooth: true
|
|
}
|
|
|
|
Label {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
text: "BearWave"
|
|
color: textMain
|
|
font.bold: true
|
|
font.pixelSize: 26
|
|
}
|
|
|
|
Label {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
text: "Internet Radio Player for KDE"
|
|
color: textMuted
|
|
font.pixelSize: 14
|
|
}
|
|
}
|
|
|
|
// --- CREDITS & LINKS ---
|
|
ColumnLayout {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
spacing: 8
|
|
Layout.topMargin: 8
|
|
Layout.bottomMargin: 8
|
|
|
|
Label {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
text: "Autor: Sebastian Palencsár"
|
|
color: textMain
|
|
font.bold: true
|
|
font.pixelSize: 14
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
spacing: 16
|
|
|
|
ToolButton {
|
|
Layout.preferredWidth: 40
|
|
Layout.preferredHeight: 40
|
|
icon.source: "qrc:/assets/ui/globe.svg"
|
|
icon.width: 19
|
|
icon.height: 19
|
|
onClicked: Qt.openUrlExternally("https://palencsar.pro")
|
|
ToolTip.visible: hovered
|
|
ToolTip.text: "Website öffnen"
|
|
background: Rectangle {
|
|
radius: 20
|
|
color: parent.hovered ? "#274261" : "#1f3147"
|
|
border.color: cardBorder
|
|
border.width: 1
|
|
}
|
|
}
|
|
|
|
ToolButton {
|
|
Layout.preferredWidth: 40
|
|
Layout.preferredHeight: 40
|
|
icon.source: "qrc:/assets/ui/github.svg"
|
|
icon.width: 19
|
|
icon.height: 19
|
|
onClicked: Qt.openUrlExternally("https://github.com/spalencsar")
|
|
ToolTip.visible: hovered
|
|
ToolTip.text: "GitHub öffnen"
|
|
background: Rectangle {
|
|
radius: 20
|
|
color: parent.hovered ? "#274261" : "#1f3147"
|
|
border.color: cardBorder
|
|
border.width: 1
|
|
}
|
|
}
|
|
|
|
ToolButton {
|
|
Layout.preferredWidth: 40
|
|
Layout.preferredHeight: 40
|
|
icon.source: "qrc:/assets/ui/linkedin.svg"
|
|
icon.width: 19
|
|
icon.height: 19
|
|
onClicked: Qt.openUrlExternally("https://www.linkedin.com/in/spalencsar/")
|
|
ToolTip.visible: hovered
|
|
ToolTip.text: "LinkedIn öffnen"
|
|
background: Rectangle {
|
|
radius: 20
|
|
color: parent.hovered ? "#274261" : "#1f3147"
|
|
border.color: cardBorder
|
|
border.width: 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- LICENSE ---
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
spacing: 8
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: "MIT License"
|
|
color: textMain
|
|
font.bold: true
|
|
}
|
|
Label {
|
|
text: "Copyright (c) 2026"
|
|
color: textMuted
|
|
font.pixelSize: 11
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
Layout.minimumHeight: 140
|
|
radius: 8
|
|
color: "#101a26"
|
|
border.color: cardBorder
|
|
|
|
ScrollView {
|
|
id: licenseScroll
|
|
anchors.fill: parent
|
|
anchors.margins: 10
|
|
clip: true
|
|
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
|
|
|
Label {
|
|
width: licenseScroll.availableWidth
|
|
color: textMain
|
|
font.pixelSize: 11
|
|
wrapMode: Text.WordWrap
|
|
text: "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Popup {
|
|
id: toastPopup
|
|
x: (root.width - width) / 2
|
|
y: root.height - height - 20
|
|
padding: 10
|
|
closePolicy: Popup.NoAutoClose
|
|
background: Rectangle {
|
|
radius: 10
|
|
color: "#24364e"
|
|
border.color: accent
|
|
}
|
|
|
|
contentItem: Label {
|
|
id: toastLabel
|
|
color: textMain
|
|
font.pixelSize: 12
|
|
}
|
|
|
|
Timer {
|
|
interval: 1400
|
|
running: toastPopup.visible
|
|
repeat: false
|
|
onTriggered: toastPopup.close()
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: contentFadeRestart
|
|
interval: 120
|
|
repeat: false
|
|
onTriggered: contentOpacity = 1.0
|
|
}
|
|
}
|