Files
bearwave/src/qml/Main.qml
Sebastian Palencsar cbd82e5ab1 refactor(qml): extract toast popup and station list panel
Move ScrollView/ListView/empty-state into StationListPanel and toast UI
into ToastPopup with show(). Main.qml is now ~271 lines; stationList
alias preserved for search debounce logic.
2026-06-20 14:27:05 +02:00

272 lines
7.1 KiB
QML

// Copyright (c) 2026 Sebastian Palencsar
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import theme 1.0
import components 1.0
ApplicationWindow {
id: root
title: qsTr("BearWave")
width: 980
height: 700
minimumWidth: 520
minimumHeight: 460
visible: true
onClosing: function(close) {
close.accepted = false
root.hide()
}
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: ""
property string selectedWorldCategory: ""
property string selectedWorldType: ""
property string countrySearchText: ""
property alias searchField: searchToolbar.searchField
property alias stationList: stationPanel.stationList
function toast(message) {
toastPopup.show(message)
}
function resetSearchFilter() {
searchTimer.stop()
if (searchField.text !== "") {
searchField.text = ""
} else if (backend && backend.filterQuery !== "") {
backend.filterQuery = ""
}
}
function getFilteredCountries() {
if (!backend || !backend.countries) return [];
var query = countrySearchText.toLowerCase().trim();
var list = [];
if (query === "") {
list.push({ name: qsTr("Top Global"), code: "GLOBAL" });
}
for (var i = 0; i < backend.countries.length; i++) {
var c = backend.countries[i];
if (query === "" || c.name.toLowerCase().indexOf(query) !== -1 || c.code.toLowerCase().indexOf(query) !== -1) {
list.push(c);
}
}
return list;
}
function activeModel() {
if (!backend) {
return []
}
if (currentPage === "favorites") {
return backend.favoriteStations
} else if (currentPage === "history") {
return backend.recentStations
} else if (currentPage === "world" && selectedWorldCategory === "") {
return []
} else {
return backend.stations
}
}
onCurrentPageChanged: {
contentOpacity = 0.85
contentFadeRestart.restart()
if (currentPage !== "search") {
resetSearchFilter()
}
if (currentPage !== "world") {
selectedWorldCategory = ""
selectedWorldType = ""
countrySearchText = ""
}
}
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: BearTheme.bgA }
GradientStop { position: 1.0; color: BearTheme.bgB }
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 10
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: headerContent.implicitHeight + 20
radius: 12
color: BearTheme.panel
border.color: BearTheme.cardBorder
ColumnLayout {
id: headerContent
anchors.fill: parent
anchors.margins: 10
spacing: 8
HeaderNavigation {
Layout.fillWidth: true
app: root
compactMode: root.compactMode
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: BearTheme.cardBorder
opacity: 0.6
}
SearchToolbar {
id: searchToolbar
Layout.fillWidth: true
app: root
compactMode: root.compactMode
searchTimer: searchTimer
}
QuickFilters {
Layout.fillWidth: true
app: root
compactMode: root.compactMode
}
Label {
Layout.fillWidth: true
visible: backend && backend.lastError.length > 0
text: backend ? (qsTr("Error: ") + backend.lastError) : ""
color: BearTheme.warn
elide: Text.ElideRight
font.pixelSize: 12
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
radius: 12
color: BearTheme.panel
border.color: BearTheme.cardBorder
clip: true
opacity: contentOpacity
Behavior on opacity {
NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
WorldCategoryHeader {
Layout.fillWidth: true
visible: currentPage === "world" && selectedWorldCategory !== ""
app: root
}
StationListPanel {
id: stationPanel
Layout.fillWidth: true
Layout.fillHeight: true
app: root
}
WorldCategories {
Layout.fillWidth: true
Layout.fillHeight: true
visible: currentPage === "world" && selectedWorldCategory === ""
app: root
compactMode: root.compactMode
}
}
}
PlayerBar {
Layout.fillWidth: true
app: root
}
}
LoadingOverlay {
app: root
}
Component.onCompleted: {
if (backend) {
backend.loadTopStations()
}
}
AddStationDialog {
id: addDialog
app: root
compactMode: root.compactMode
}
EditStationDialog {
id: editDialog
app: root
compactMode: root.compactMode
}
AboutDialog {
id: aboutDialog
compactMode: root.compactMode
}
ToastPopup {
id: toastPopup
window: root
}
Timer {
id: searchTimer
interval: 600
repeat: false
onTriggered: {
var text = searchField.text.trim()
if (text.length < 2 || !backend) return
if (currentPage === "search" || stationList.count === 0) {
currentPage = "search"
backend.searchStations(text)
}
}
}
Timer {
id: contentFadeRestart
interval: 120
repeat: false
onTriggered: contentOpacity = 1.0
}
}