mirror of
https://github.com/spalencsar/bearwave.git
synced 2026-07-06 14:24:14 +02:00
feat(ui): Mac-inspired layout with sidebar, details panel, and About page
Replace the modal About dialog with an embedded About page and introduce a three-pane desktop layout: persistent sidebar navigation, flat station list, right-side details panel, and compact player bar. Load the GPLv3 license text from C++ and expose it to QML as context data. Normalize i18n source strings to English, complete German and Russian translations (133 strings each), and centralize page navigation helpers in Main.qml. Simplify compact HeaderNavigation and extend .gitignore for local build artifacts.
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@@ -4,7 +4,11 @@ build.md
|
|||||||
|
|
||||||
## Build directories
|
## Build directories
|
||||||
build/
|
build/
|
||||||
|
build-about/
|
||||||
build-ci/
|
build-ci/
|
||||||
|
build-fix/
|
||||||
|
build-maclayout/
|
||||||
|
build-test/
|
||||||
build-arch-ci/
|
build-arch-ci/
|
||||||
build-dir/
|
build-dir/
|
||||||
cmake-build-*/
|
cmake-build-*/
|
||||||
@@ -36,6 +40,11 @@ test-*.json
|
|||||||
pkg/
|
pkg/
|
||||||
src/bearwave/
|
src/bearwave/
|
||||||
bearwave/
|
bearwave/
|
||||||
|
flatpak_repo/
|
||||||
|
.flatpak-builder/
|
||||||
|
.flatpak-builder-manifest.json
|
||||||
|
scratch/
|
||||||
|
spalencsar-README.md
|
||||||
*.pkg.tar.zst
|
*.pkg.tar.zst
|
||||||
*.pkg.tar.zst.sig
|
*.pkg.tar.zst.sig
|
||||||
*.pkg.tar.xz
|
*.pkg.tar.xz
|
||||||
|
|||||||
@@ -10,14 +10,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add Russian language support and translation (thanks to [@aaly11](https://github.com/aaly11)).
|
- Add Russian language support and translation (thanks to [@aaly11](https://github.com/aaly11)).
|
||||||
|
- Add a Mac-inspired desktop layout with persistent sidebar navigation, flat station rows, right-side station details, and a compact now-playing bar.
|
||||||
|
- Add an embedded About page with full GPLv3 license text loaded from the bundled `LICENSE` resource and a third-party technologies section.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Flatpak repository signing key replaced with `Bearwave App <dev@bearwave.app>` (`5BAA384577671E45`).
|
- Flatpak repository signing key replaced with `Bearwave App <dev@bearwave.app>` (`5BAA384577671E45`).
|
||||||
|
- Replace the modal About dialog with an in-app About page reachable from sidebar and compact navigation.
|
||||||
|
- Refresh the QML visual theme toward a neutral dark palette and align the main layout more closely with the macOS BearWave structure.
|
||||||
|
- Use the BearWave line logo asset in the sidebar and remove duplicate branding from the top search toolbar.
|
||||||
|
- Make the top search/filter area and bottom player bar more responsive to narrower desktop window sizes.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix German translation contexts broken after the QML refactor (thanks to [@aaly11](https://github.com/aaly11)).
|
- Fix German translation contexts broken after the QML refactor (thanks to [@aaly11](https://github.com/aaly11)).
|
||||||
|
- Fix the embedded GPL text failing to load in QML by loading the resource in C++ and exposing it to QML as context data.
|
||||||
|
- Fix the About license view formatting so the text uses the available width without an oversized empty box.
|
||||||
|
- Fix right-side station details and top toolbar clipping in the Mac-style layout.
|
||||||
|
|
||||||
## Version notes (1.0.3 → 1.0.5)
|
## Version notes (1.0.3 → 1.0.5)
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ If you changed QML:
|
|||||||
qmllint src/qml/Main.qml src/qml/components/*.qml src/qml/theme/BearTheme.qml
|
qmllint src/qml/Main.qml src/qml/components/*.qml src/qml/theme/BearTheme.qml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For UI layout work that should be tested separately from the default build tree:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -S . -B build-maclayout -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build-maclayout -j"$(nproc)"
|
||||||
|
./build-maclayout/src/bearwave
|
||||||
|
```
|
||||||
|
|
||||||
Run unit tests after backend changes:
|
Run unit tests after backend changes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -51,6 +59,7 @@ ctest --test-dir build --output-on-failure
|
|||||||
- Keep network operations asynchronous.
|
- Keep network operations asynchronous.
|
||||||
- Preserve MPRIS behavior and user state compatibility.
|
- Preserve MPRIS behavior and user state compatibility.
|
||||||
- When changing resources, register them in `src/qml.qrc`.
|
- When changing resources, register them in `src/qml.qrc`.
|
||||||
|
- When changing the About page, keep the full GPL text resource visible and keep third-party technology notes accurate.
|
||||||
|
|
||||||
## Manual Checks
|
## Manual Checks
|
||||||
|
|
||||||
@@ -63,6 +72,7 @@ Please smoke-test the area you changed when possible:
|
|||||||
- favorites persist across restart
|
- favorites persist across restart
|
||||||
- resume restores last station and volume
|
- resume restores last station and volume
|
||||||
- tray and MPRIS behavior still work
|
- tray and MPRIS behavior still work
|
||||||
|
- About page opens from sidebar/compact navigation, social links work, and the GPL license text is scrollable
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
|
|
||||||
|
|||||||
24
README.md
24
README.md
@@ -20,7 +20,7 @@ Why not 1.0.4 on GitHub? [1.0.4](CHANGELOG.md#104---2026-06-20) exists as a **gi
|
|||||||
| Main window | Station browser |
|
| Main window | Station browser |
|
||||||
| ----------------------------------------- | -------------------------------------------- |
|
| ----------------------------------------- | -------------------------------------------- |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  | |
|
|  | |
|
||||||
|
|
||||||
Screenshots: KDE Plasma on Linux.
|
Screenshots: KDE Plasma on Linux.
|
||||||
@@ -163,7 +163,8 @@ BearWave intentionally does not aim to be:
|
|||||||
- MPRIS integration for Plasma media controls and media keys
|
- MPRIS integration for Plasma media controls and media keys
|
||||||
- system tray integration for background playback
|
- system tray integration for background playback
|
||||||
- desktop notifications for song/track changes with local cover art caching
|
- desktop notifications for song/track changes with local cover art caching
|
||||||
- embedded About dialog with links and GNU GPLv3 license text
|
- Mac-inspired three-pane desktop layout with sidebar navigation, station list, details panel, and compact now-playing bar
|
||||||
|
- embedded About page with author links, full GNU GPLv3 license text, and third-party technology notes
|
||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
@@ -216,14 +217,14 @@ Add distributions here only after explicit testing, not by assumption alone.
|
|||||||
|
|
||||||
### Language Support
|
### Language Support
|
||||||
|
|
||||||
BearWave currently supports English and German.
|
BearWave currently supports English, German, and Russian.
|
||||||
|
|
||||||
- the application uses the system locale to select its UI language
|
- the application uses the system locale to select its UI language
|
||||||
- English is the base UI language
|
- English is the base UI language
|
||||||
- German is provided as a bundled translation
|
- German and Russian are provided as bundled translations
|
||||||
- README, repository metadata, and development-facing material remain in English
|
- README, repository metadata, and development-facing material remain in English
|
||||||
|
|
||||||
If the system language is German, BearWave appears in German. Otherwise it falls back to English.
|
If the system language matches a bundled translation, BearWave uses that language. Otherwise it falls back to English.
|
||||||
|
|
||||||
## Installation Status
|
## Installation Status
|
||||||
|
|
||||||
@@ -290,6 +291,16 @@ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
|||||||
cmake --build build -j"$(nproc)"
|
cmake --build build -j"$(nproc)"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### UI layout development build
|
||||||
|
|
||||||
|
For layout work that should not disturb the normal `build/` directory, a separate build tree can be used:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -S . -B build-maclayout -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build-maclayout -j"$(nproc)"
|
||||||
|
./build-maclayout/src/bearwave
|
||||||
|
```
|
||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
|
|
||||||
From the build directory:
|
From the build directory:
|
||||||
@@ -425,13 +436,14 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for local build and review expectations.
|
|||||||
## Development Notes
|
## Development Notes
|
||||||
|
|
||||||
- main UI shell: `src/qml/Main.qml`
|
- main UI shell: `src/qml/Main.qml`
|
||||||
- QML components: `src/qml/components/` (navigation, search, station cards, player bar, dialogs)
|
- QML components: `src/qml/components/` (navigation, search, station rows, detail panel, About page, player bar, dialogs)
|
||||||
- theme singleton: `src/qml/theme/BearTheme.qml`
|
- theme singleton: `src/qml/theme/BearTheme.qml`
|
||||||
- backend orchestration: `src/radiobackend.cpp`
|
- backend orchestration: `src/radiobackend.cpp`
|
||||||
- stream playback: `src/bearplayer.cpp`
|
- stream playback: `src/bearplayer.cpp`
|
||||||
- API layer: `src/radiobrowser.cpp`
|
- API layer: `src/radiobrowser.cpp`
|
||||||
- MPRIS adapter: `src/mprisadaptor.cpp`
|
- MPRIS adapter: `src/mprisadaptor.cpp`
|
||||||
- desktop notifications: `src/notificationmanager.cpp`
|
- desktop notifications: `src/notificationmanager.cpp`
|
||||||
|
- embedded GPL license resource: `qrc:/assets/legal/gpl-3.0.txt` from `LICENSE`
|
||||||
- unit tests: `tests/`
|
- unit tests: `tests/`
|
||||||
|
|
||||||
See [CHANGELOG.md](CHANGELOG.md) for release history. For contributor and agent guardrails, see `AGENTS.md`.
|
See [CHANGELOG.md](CHANGELOG.md) for release history. For contributor and agent guardrails, see `AGENTS.md`.
|
||||||
|
|||||||
BIN
screenshots/screen6.png
Normal file
BIN
screenshots/screen6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
@@ -6,6 +6,7 @@
|
|||||||
#include <QDBusConnectionInterface>
|
#include <QDBusConnectionInterface>
|
||||||
#include <QDBusMessage>
|
#include <QDBusMessage>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QFile>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
@@ -56,9 +57,17 @@ int main(int argc, char *argv[])
|
|||||||
engine.addImportPath(QStringLiteral("qrc:/qml"));
|
engine.addImportPath(QStringLiteral("qrc:/qml"));
|
||||||
|
|
||||||
RadioBackend backend;
|
RadioBackend backend;
|
||||||
|
QFile licenseFile(QStringLiteral(":/assets/legal/gpl-3.0.txt"));
|
||||||
|
QString licenseText;
|
||||||
|
if (licenseFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
licenseText = QString::fromUtf8(licenseFile.readAll());
|
||||||
|
} else {
|
||||||
|
qWarning() << "Failed to load embedded GPLv3 license text";
|
||||||
|
}
|
||||||
engine.rootContext()->setContextProperty("radioBackend", &backend);
|
engine.rootContext()->setContextProperty("radioBackend", &backend);
|
||||||
engine.rootContext()->setContextProperty("bearwaveVersion", QStringLiteral(BEARWAVE_VERSION));
|
engine.rootContext()->setContextProperty("bearwaveVersion", QStringLiteral(BEARWAVE_VERSION));
|
||||||
engine.rootContext()->setContextProperty("bearwaveBuildId", QStringLiteral(BEARWAVE_GIT_HASH));
|
engine.rootContext()->setContextProperty("bearwaveBuildId", QStringLiteral(BEARWAVE_GIT_HASH));
|
||||||
|
engine.rootContext()->setContextProperty("bearwaveLicenseText", licenseText);
|
||||||
|
|
||||||
auto *mprisRoot = new MprisRootAdaptor(&backend, &app);
|
auto *mprisRoot = new MprisRootAdaptor(&backend, &app);
|
||||||
auto *mprisPlayer = new MprisPlayerAdaptor(&backend);
|
auto *mprisPlayer = new MprisPlayerAdaptor(&backend);
|
||||||
|
|||||||
@@ -11,12 +11,15 @@
|
|||||||
<file>qml/components/WorldCategoryHeader.qml</file>
|
<file>qml/components/WorldCategoryHeader.qml</file>
|
||||||
<file>qml/components/WorldCategories.qml</file>
|
<file>qml/components/WorldCategories.qml</file>
|
||||||
<file>qml/components/PlayerBar.qml</file>
|
<file>qml/components/PlayerBar.qml</file>
|
||||||
<file>qml/components/AboutDialog.qml</file>
|
<file>qml/components/AboutPage.qml</file>
|
||||||
<file>qml/components/AddStationDialog.qml</file>
|
<file>qml/components/AddStationDialog.qml</file>
|
||||||
<file>qml/components/EditStationDialog.qml</file>
|
<file>qml/components/EditStationDialog.qml</file>
|
||||||
<file>qml/components/LoadingOverlay.qml</file>
|
<file>qml/components/LoadingOverlay.qml</file>
|
||||||
<file>qml/components/ToastPopup.qml</file>
|
<file>qml/components/ToastPopup.qml</file>
|
||||||
<file>qml/components/StationListPanel.qml</file>
|
<file>qml/components/StationListPanel.qml</file>
|
||||||
|
<file>qml/components/SidebarNavigation.qml</file>
|
||||||
|
<file>qml/components/SidebarButton.qml</file>
|
||||||
|
<file>qml/components/StationDetailsPanel.qml</file>
|
||||||
<file>qml/utils/FlagUtils.js</file>
|
<file>qml/utils/FlagUtils.js</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
@@ -25,5 +28,6 @@
|
|||||||
<file alias="assets/ui/globe.svg">../assets/ui/globe.svg</file>
|
<file alias="assets/ui/globe.svg">../assets/ui/globe.svg</file>
|
||||||
<file alias="assets/ui/github.svg">../assets/ui/github.svg</file>
|
<file alias="assets/ui/github.svg">../assets/ui/github.svg</file>
|
||||||
<file alias="assets/ui/linkedin.svg">../assets/ui/linkedin.svg</file>
|
<file alias="assets/ui/linkedin.svg">../assets/ui/linkedin.svg</file>
|
||||||
|
<file alias="assets/legal/gpl-3.0.txt">../LICENSE</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
186
src/qml/Main.qml
186
src/qml/Main.qml
@@ -11,9 +11,9 @@ import components 1.0
|
|||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
id: root
|
id: root
|
||||||
title: qsTr("BearWave")
|
title: qsTr("BearWave")
|
||||||
width: 980
|
width: 1400
|
||||||
height: 700
|
height: 720
|
||||||
minimumWidth: 520
|
minimumWidth: 900
|
||||||
minimumHeight: 460
|
minimumHeight: 460
|
||||||
visible: true
|
visible: true
|
||||||
|
|
||||||
@@ -26,7 +26,9 @@ ApplicationWindow {
|
|||||||
property var backend: (typeof radioBackend !== "undefined" ? radioBackend : null)
|
property var backend: (typeof radioBackend !== "undefined" ? radioBackend : null)
|
||||||
readonly property string appVersion: (typeof bearwaveVersion !== "undefined" ? ("" + bearwaveVersion) : Qt.application.version)
|
readonly property string appVersion: (typeof bearwaveVersion !== "undefined" ? ("" + bearwaveVersion) : Qt.application.version)
|
||||||
readonly property string appBuildId: (typeof bearwaveBuildId !== "undefined" ? ("" + bearwaveBuildId) : "?")
|
readonly property string appBuildId: (typeof bearwaveBuildId !== "undefined" ? ("" + bearwaveBuildId) : "?")
|
||||||
property bool compactMode: width < 780
|
readonly property string appLicenseText: (typeof bearwaveLicenseText !== "undefined" ? ("" + bearwaveLicenseText) : "")
|
||||||
|
property bool compactMode: width < 900
|
||||||
|
property var selectedStation: null
|
||||||
property real contentOpacity: 1.0
|
property real contentOpacity: 1.0
|
||||||
property string activeQuickFilter: ""
|
property string activeQuickFilter: ""
|
||||||
property string selectedWorldCategory: ""
|
property string selectedWorldCategory: ""
|
||||||
@@ -36,12 +38,54 @@ ApplicationWindow {
|
|||||||
property alias stationList: stationPanel.stationList
|
property alias stationList: stationPanel.stationList
|
||||||
property alias addDialog: addDialogPane
|
property alias addDialog: addDialogPane
|
||||||
property alias editDialog: editDialogPane
|
property alias editDialog: editDialogPane
|
||||||
property alias aboutDialog: aboutDialogPane
|
|
||||||
|
|
||||||
function toast(message) {
|
function toast(message) {
|
||||||
toastPopup.show(message)
|
toastPopup.show(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function navigateToTop() {
|
||||||
|
if (!backend) return
|
||||||
|
currentPage = "top"
|
||||||
|
activeQuickFilter = ""
|
||||||
|
backend.loadTopStations()
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToGerman() {
|
||||||
|
if (!backend) return
|
||||||
|
currentPage = "german"
|
||||||
|
activeQuickFilter = ""
|
||||||
|
backend.loadGermanStations()
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToDutch() {
|
||||||
|
if (!backend) return
|
||||||
|
currentPage = "dutch"
|
||||||
|
activeQuickFilter = ""
|
||||||
|
backend.loadDutchStations()
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToWorld() {
|
||||||
|
currentPage = "world"
|
||||||
|
activeQuickFilter = ""
|
||||||
|
selectedWorldCategory = ""
|
||||||
|
selectedWorldType = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToFavorites() {
|
||||||
|
currentPage = "favorites"
|
||||||
|
activeQuickFilter = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToHistory() {
|
||||||
|
currentPage = "history"
|
||||||
|
activeQuickFilter = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToAbout() {
|
||||||
|
currentPage = "about"
|
||||||
|
activeQuickFilter = ""
|
||||||
|
}
|
||||||
|
|
||||||
function resetSearchFilter() {
|
function resetSearchFilter() {
|
||||||
searchTimer.stop()
|
searchTimer.stop()
|
||||||
if (searchField.text !== "") {
|
if (searchField.text !== "") {
|
||||||
@@ -85,6 +129,9 @@ ApplicationWindow {
|
|||||||
onCurrentPageChanged: {
|
onCurrentPageChanged: {
|
||||||
contentOpacity = 0.85
|
contentOpacity = 0.85
|
||||||
contentFadeRestart.restart()
|
contentFadeRestart.restart()
|
||||||
|
if (currentPage !== "about") {
|
||||||
|
selectedStation = null
|
||||||
|
}
|
||||||
if (currentPage !== "search") {
|
if (currentPage !== "search") {
|
||||||
resetSearchFilter()
|
resetSearchFilter()
|
||||||
}
|
}
|
||||||
@@ -111,34 +158,61 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
gradient: Gradient {
|
color: BearTheme.bgA
|
||||||
GradientStop { position: 0.0; color: BearTheme.bgA }
|
|
||||||
GradientStop { position: 1.0; color: BearTheme.bgB }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
SidebarNavigation {
|
||||||
anchors.fill: parent
|
id: sidebarNav
|
||||||
anchors.margins: 12
|
visible: !root.compactMode
|
||||||
spacing: 10
|
width: root.compactMode ? 0 : 200
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.margins: 0
|
||||||
|
app: root
|
||||||
|
compactMode: root.compactMode
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: workspace
|
||||||
|
anchors.left: root.compactMode ? parent.left : sidebarNav.right
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.margins: 0
|
||||||
|
anchors.leftMargin: root.compactMode ? 0 : 0
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
id: headerPanel
|
||||||
Layout.preferredHeight: headerContent.implicitHeight + 20
|
height: currentPage === "about" && !root.compactMode ? 1 : headerContent.implicitHeight + 18
|
||||||
radius: 12
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
radius: 0
|
||||||
color: BearTheme.panel
|
color: BearTheme.panel
|
||||||
border.color: BearTheme.cardBorder
|
border.color: "transparent"
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
height: 1
|
||||||
|
color: BearTheme.cardBorder
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: headerContent
|
id: headerContent
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 10
|
anchors.margins: 9
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
visible: !(currentPage === "about" && !root.compactMode)
|
||||||
|
|
||||||
HeaderNavigation {
|
HeaderNavigation {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
app: root
|
app: root
|
||||||
compactMode: root.compactMode
|
compactMode: root.compactMode
|
||||||
|
visible: root.compactMode
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -146,6 +220,7 @@ ApplicationWindow {
|
|||||||
Layout.preferredHeight: 1
|
Layout.preferredHeight: 1
|
||||||
color: BearTheme.cardBorder
|
color: BearTheme.cardBorder
|
||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
|
visible: root.compactMode
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchToolbar {
|
SearchToolbar {
|
||||||
@@ -154,12 +229,14 @@ ApplicationWindow {
|
|||||||
app: root
|
app: root
|
||||||
compactMode: root.compactMode
|
compactMode: root.compactMode
|
||||||
searchTimer: searchTimer
|
searchTimer: searchTimer
|
||||||
|
visible: currentPage !== "about"
|
||||||
}
|
}
|
||||||
|
|
||||||
QuickFilters {
|
QuickFilters {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
app: root
|
app: root
|
||||||
compactMode: root.compactMode
|
compactMode: root.compactMode
|
||||||
|
visible: currentPage !== "about"
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -173,12 +250,23 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
PlayerBar {
|
||||||
Layout.fillWidth: true
|
id: playerPanel
|
||||||
Layout.fillHeight: true
|
height: implicitHeight
|
||||||
radius: 12
|
anchors.left: parent.left
|
||||||
color: BearTheme.panel
|
anchors.right: parent.right
|
||||||
border.color: BearTheme.cardBorder
|
anchors.bottom: parent.bottom
|
||||||
|
app: root
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: contentArea
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: headerPanel.bottom
|
||||||
|
anchors.bottom: playerPanel.top
|
||||||
|
anchors.topMargin: 0
|
||||||
|
anchors.bottomMargin: 0
|
||||||
clip: true
|
clip: true
|
||||||
opacity: contentOpacity
|
opacity: contentOpacity
|
||||||
|
|
||||||
@@ -186,10 +274,32 @@ ApplicationWindow {
|
|||||||
NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
|
NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: stationPanelFrame
|
||||||
|
visible: currentPage !== "about"
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: root.compactMode ? parent.right : stationDetails.left
|
||||||
|
anchors.rightMargin: root.compactMode ? 0 : 0
|
||||||
|
radius: 0
|
||||||
|
color: BearTheme.panel
|
||||||
|
border.color: "transparent"
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
width: 1
|
||||||
|
color: BearTheme.cardBorder
|
||||||
|
visible: !root.compactMode
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 10
|
anchors.margins: 10
|
||||||
spacing: 10
|
spacing: 0
|
||||||
|
|
||||||
WorldCategoryHeader {
|
WorldCategoryHeader {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -214,10 +324,25 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerBar {
|
StationDetailsPanel {
|
||||||
Layout.fillWidth: true
|
id: stationDetails
|
||||||
|
visible: !root.compactMode && currentPage !== "about"
|
||||||
|
width: 350
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
app: root
|
app: root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AboutPage {
|
||||||
|
visible: currentPage === "about"
|
||||||
|
anchors.fill: parent
|
||||||
|
compactMode: root.compactMode
|
||||||
|
appVersion: root.appVersion
|
||||||
|
buildId: root.appBuildId
|
||||||
|
licenseText: root.appLicenseText
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadingOverlay {
|
LoadingOverlay {
|
||||||
@@ -242,13 +367,6 @@ ApplicationWindow {
|
|||||||
compactMode: root.compactMode
|
compactMode: root.compactMode
|
||||||
}
|
}
|
||||||
|
|
||||||
AboutDialog {
|
|
||||||
id: aboutDialogPane
|
|
||||||
compactMode: root.compactMode
|
|
||||||
appVersion: root.appVersion
|
|
||||||
buildId: root.appBuildId
|
|
||||||
}
|
|
||||||
|
|
||||||
ToastPopup {
|
ToastPopup {
|
||||||
id: toastPopup
|
id: toastPopup
|
||||||
window: root
|
window: root
|
||||||
|
|||||||
@@ -1,212 +0,0 @@
|
|||||||
// 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
|
|
||||||
|
|
||||||
Dialog {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property bool compactMode
|
|
||||||
property string appVersion: Qt.application.version
|
|
||||||
property string buildId: "?"
|
|
||||||
|
|
||||||
readonly property string versionLine: {
|
|
||||||
var version = root.appVersion
|
|
||||||
if (version === undefined || version === null || version === "")
|
|
||||||
version = Qt.application.version
|
|
||||||
var build = root.buildId
|
|
||||||
if (build === undefined || build === null || build === "")
|
|
||||||
build = "?"
|
|
||||||
return qsTr("Version") + " " + String(version) + " · " + qsTr("build") + " " + String(build)
|
|
||||||
}
|
|
||||||
|
|
||||||
modal: true
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: compactMode ? 360 : 520
|
|
||||||
height: compactMode ? 560 : 680
|
|
||||||
standardButtons: Dialog.Ok
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
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: qsTr("BearWave")
|
|
||||||
color: BearTheme.textMain
|
|
||||||
font.bold: true
|
|
||||||
font.pixelSize: 26
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: qsTr("Internet Radio Player for KDE")
|
|
||||||
color: BearTheme.textMuted
|
|
||||||
font.pixelSize: 14
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: root.versionLine
|
|
||||||
color: BearTheme.textMuted
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
spacing: 8
|
|
||||||
Layout.topMargin: 8
|
|
||||||
Layout.bottomMargin: 8
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: qsTr("Author: Sebastian Palencsár")
|
|
||||||
color: BearTheme.textMain
|
|
||||||
font.bold: true
|
|
||||||
font.pixelSize: 14
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
ToolButton {
|
|
||||||
Layout.preferredWidth: 40
|
|
||||||
Layout.preferredHeight: 40
|
|
||||||
onClicked: Qt.openUrlExternally("https://palencsar.pro")
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Open website")
|
|
||||||
contentItem: Item {
|
|
||||||
Image {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 19
|
|
||||||
height: 19
|
|
||||||
source: "qrc:/assets/ui/globe.svg"
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
smooth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
background: Rectangle {
|
|
||||||
radius: 20
|
|
||||||
color: parent.hovered ? "#274261" : "#1f3147"
|
|
||||||
border.color: BearTheme.cardBorder
|
|
||||||
border.width: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolButton {
|
|
||||||
Layout.preferredWidth: 40
|
|
||||||
Layout.preferredHeight: 40
|
|
||||||
onClicked: Qt.openUrlExternally("https://github.com/spalencsar")
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Open GitHub")
|
|
||||||
contentItem: Item {
|
|
||||||
Image {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 19
|
|
||||||
height: 19
|
|
||||||
source: "qrc:/assets/ui/github.svg"
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
smooth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
background: Rectangle {
|
|
||||||
radius: 20
|
|
||||||
color: parent.hovered ? "#274261" : "#1f3147"
|
|
||||||
border.color: BearTheme.cardBorder
|
|
||||||
border.width: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolButton {
|
|
||||||
Layout.preferredWidth: 40
|
|
||||||
Layout.preferredHeight: 40
|
|
||||||
onClicked: Qt.openUrlExternally("https://www.linkedin.com/in/spalencsar/")
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Open LinkedIn")
|
|
||||||
contentItem: Item {
|
|
||||||
Image {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 19
|
|
||||||
height: 19
|
|
||||||
source: "qrc:/assets/ui/linkedin.svg"
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
smooth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
background: Rectangle {
|
|
||||||
radius: 20
|
|
||||||
color: parent.hovered ? "#274261" : "#1f3147"
|
|
||||||
border.color: BearTheme.cardBorder
|
|
||||||
border.width: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: qsTr("GNU GPLv3 License")
|
|
||||||
color: BearTheme.textMain
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: qsTr("Copyright (c) 2026")
|
|
||||||
color: BearTheme.textMuted
|
|
||||||
font.pixelSize: 11
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.minimumHeight: 140
|
|
||||||
radius: 8
|
|
||||||
color: "#101a26"
|
|
||||||
border.color: BearTheme.cardBorder
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
id: licenseScroll
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 10
|
|
||||||
clip: true
|
|
||||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
|
||||||
|
|
||||||
Label {
|
|
||||||
width: Math.max(licenseScroll.width, 1)
|
|
||||||
color: BearTheme.textMain
|
|
||||||
font.pixelSize: 11
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
text: "This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
347
src/qml/components/AboutPage.qml
Normal file
347
src/qml/components/AboutPage.qml
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property bool compactMode
|
||||||
|
property string appVersion: Qt.application.version
|
||||||
|
property string buildId: "?"
|
||||||
|
property string licenseText: ""
|
||||||
|
|
||||||
|
readonly property string versionLine: {
|
||||||
|
var version = root.appVersion
|
||||||
|
if (version === undefined || version === null || version === "")
|
||||||
|
version = Qt.application.version
|
||||||
|
var build = root.buildId
|
||||||
|
if (build === undefined || build === null || build === "")
|
||||||
|
build = "?"
|
||||||
|
return qsTr("Version") + " " + String(version) + " · " + qsTr("Build") + " " + String(build)
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var thirdPartyItems: [
|
||||||
|
{
|
||||||
|
name: qsTr("Qt 6"),
|
||||||
|
usage: qsTr("Application framework and UI runtime: Core, DBus, Network, Quick, Quick Controls 2, Widgets, Multimedia, and LinguistTools."),
|
||||||
|
license: qsTr("Subject to the upstream licenses of the installed Qt distribution.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: qsTr("Qt Multimedia backend"),
|
||||||
|
usage: qsTr("Audio playback runs through Qt Multimedia and the backend available on the system, such as GStreamer or FFmpeg."),
|
||||||
|
license: qsTr("Backend components are provided by the operating system or Qt build and retain their own licenses.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: qsTr("Radio Browser API"),
|
||||||
|
usage: qsTr("Public station directory API for station data and stream discovery. BearWave does not bundle any Radio Browser server code."),
|
||||||
|
license: qsTr("External service or API; subject to the upstream project and service terms.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: qsTr("freedesktop.org integrations"),
|
||||||
|
usage: qsTr("D-Bus, MPRIS, desktop notifications, desktop entry, and AppStream metadata."),
|
||||||
|
license: qsTr("Uses standards and interfaces for desktop integration; no third-party implementation is bundled.")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
id: pageScroll
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
width: Math.max(pageScroll.availableWidth, 1)
|
||||||
|
spacing: root.compactMode ? 18 : 22
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: root.compactMode ? 18 : 34
|
||||||
|
Layout.leftMargin: root.compactMode ? 18 : 46
|
||||||
|
Layout.rightMargin: root.compactMode ? 18 : 46
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
|
Image {
|
||||||
|
Layout.preferredWidth: root.compactMode ? 58 : 72
|
||||||
|
Layout.preferredHeight: root.compactMode ? 58 : 72
|
||||||
|
sourceSize.width: root.compactMode ? 58 : 72
|
||||||
|
sourceSize.height: root.compactMode ? 58 : 72
|
||||||
|
source: "qrc:/assets/app/bearwave.png"
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
mipmap: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Image {
|
||||||
|
Layout.preferredWidth: root.compactMode ? 170 : 210
|
||||||
|
Layout.preferredHeight: root.compactMode ? 40 : 48
|
||||||
|
source: "qrc:/assets/app/bearwave_line.png"
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
horizontalAlignment: Image.AlignLeft
|
||||||
|
smooth: true
|
||||||
|
mipmap: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("Internet radio player for KDE")
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.pixelSize: root.compactMode ? 14 : 16
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.versionLine
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.pixelSize: 12
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: root.compactMode ? 18 : 46
|
||||||
|
Layout.rightMargin: root.compactMode ? 18 : 46
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
color: BearTheme.cardBorder
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: root.compactMode ? 18 : 46
|
||||||
|
Layout.rightMargin: root.compactMode ? 18 : 46
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Author")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 11
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 14
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("Sebastian Palencsár")
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 15
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
Layout.preferredWidth: 36
|
||||||
|
Layout.preferredHeight: 30
|
||||||
|
onClicked: Qt.openUrlExternally("https://palencsar.pro")
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Open website")
|
||||||
|
contentItem: Item {
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 18
|
||||||
|
height: 18
|
||||||
|
source: "qrc:/assets/ui/globe.svg"
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
Layout.preferredWidth: 36
|
||||||
|
Layout.preferredHeight: 30
|
||||||
|
onClicked: Qt.openUrlExternally("https://github.com/spalencsar")
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Open GitHub")
|
||||||
|
contentItem: Item {
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 18
|
||||||
|
height: 18
|
||||||
|
source: "qrc:/assets/ui/github.svg"
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
Layout.preferredWidth: 36
|
||||||
|
Layout.preferredHeight: 30
|
||||||
|
onClicked: Qt.openUrlExternally("https://www.linkedin.com/in/spalencsar/")
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Open LinkedIn")
|
||||||
|
contentItem: Item {
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 18
|
||||||
|
height: 18
|
||||||
|
source: "qrc:/assets/ui/linkedin.svg"
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: root.compactMode ? 18 : 46
|
||||||
|
Layout.rightMargin: root.compactMode ? 18 : 46
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
color: BearTheme.cardBorder
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: root.compactMode ? 18 : 46
|
||||||
|
Layout.rightMargin: root.compactMode ? 18 : 46
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Technologies and third-party components")
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 14
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("BearWave uses the following frameworks, services, and desktop standards. These components are not relicensed by BearWave.")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.pixelSize: 12
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.thirdPartyItems
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: thirdPartyRow.implicitHeight + 16
|
||||||
|
radius: 6
|
||||||
|
color: "#24252b"
|
||||||
|
border.color: BearTheme.cardBorder
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: thirdPartyRow
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: 12
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
spacing: 14
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.preferredWidth: root.compactMode ? 120 : 180
|
||||||
|
text: modelData.name
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 13
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: modelData.usage
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.pixelSize: 12
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: modelData.license
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.pixelSize: 11
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: root.compactMode ? 18 : 46
|
||||||
|
Layout.rightMargin: root.compactMode ? 18 : 46
|
||||||
|
Layout.bottomMargin: root.compactMode ? 18 : 34
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("GNU GPLv3 License")
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 14
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Copyright (c) 2026")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.pixelSize: 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: root.compactMode ? 240 : 280
|
||||||
|
radius: 6
|
||||||
|
color: "#202127"
|
||||||
|
border.color: BearTheme.cardBorder
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
id: licenseScroll
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
clip: true
|
||||||
|
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
id: licenseTextArea
|
||||||
|
width: Math.max(licenseScroll.availableWidth, 1)
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: true
|
||||||
|
textFormat: TextEdit.PlainText
|
||||||
|
wrapMode: TextEdit.Wrap
|
||||||
|
text: root.licenseText.length > 0 ? root.licenseText : qsTr("License text could not be loaded.")
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.family: "monospace"
|
||||||
|
font.pixelSize: 12
|
||||||
|
background: Rectangle {
|
||||||
|
color: "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,121 +13,13 @@ Item {
|
|||||||
required property var app
|
required property var app
|
||||||
required property bool compactMode
|
required property bool compactMode
|
||||||
|
|
||||||
implicitWidth: compactMode ? compactNav.implicitWidth : desktopNav.implicitWidth
|
implicitWidth: compactNav.implicitWidth
|
||||||
implicitHeight: compactMode ? compactNav.implicitHeight : desktopNav.implicitHeight
|
implicitHeight: compactNav.implicitHeight
|
||||||
|
|
||||||
function loadTop() {
|
|
||||||
if (!app.backend) return
|
|
||||||
app.currentPage = "top"
|
|
||||||
app.activeQuickFilter = ""
|
|
||||||
app.backend.loadTopStations()
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadGerman() {
|
|
||||||
if (!app.backend) return
|
|
||||||
app.currentPage = "german"
|
|
||||||
app.activeQuickFilter = ""
|
|
||||||
app.backend.loadGermanStations()
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadDutch() {
|
|
||||||
if (!app.backend) return
|
|
||||||
app.currentPage = "dutch"
|
|
||||||
app.activeQuickFilter = ""
|
|
||||||
app.backend.loadDutchStations()
|
|
||||||
}
|
|
||||||
|
|
||||||
function showFavorites() {
|
|
||||||
app.currentPage = "favorites"
|
|
||||||
app.activeQuickFilter = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function showHistory() {
|
|
||||||
app.currentPage = "history"
|
|
||||||
app.activeQuickFilter = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: desktopNav
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
visible: !root.compactMode
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Image {
|
|
||||||
Layout.preferredWidth: 112
|
|
||||||
Layout.preferredHeight: 40
|
|
||||||
source: "qrc:/assets/app/bearwave_line.png"
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
smooth: true
|
|
||||||
mipmap: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.preferredWidth: 1
|
|
||||||
Layout.fillHeight: true
|
|
||||||
color: BearTheme.cardBorder
|
|
||||||
opacity: 0.6
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("Top")
|
|
||||||
highlighted: app.currentPage === "top"
|
|
||||||
onClicked: loadTop()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("DE")
|
|
||||||
highlighted: app.currentPage === "german"
|
|
||||||
onClicked: loadGerman()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("NL")
|
|
||||||
highlighted: app.currentPage === "dutch"
|
|
||||||
onClicked: loadDutch()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("Favorites")
|
|
||||||
highlighted: app.currentPage === "favorites"
|
|
||||||
onClicked: showFavorites()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("History")
|
|
||||||
highlighted: app.currentPage === "history"
|
|
||||||
onClicked: showHistory()
|
|
||||||
}
|
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("Manual +")
|
|
||||||
onClicked: app.addDialog.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("About")
|
|
||||||
onClicked: app.aboutDialog.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
visible: app.backend && app.backend.canResumeLastStation
|
|
||||||
text: qsTr("Resume")
|
|
||||||
onClicked: {
|
|
||||||
if (app.backend) {
|
|
||||||
app.backend.resumeLastStation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: compactNav
|
id: compactNav
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
visible: root.compactMode
|
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -152,7 +44,8 @@ Item {
|
|||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("About")
|
text: qsTr("About")
|
||||||
onClicked: app.aboutDialog.open()
|
highlighted: app.currentPage === "about"
|
||||||
|
onClicked: app.navigateToAbout()
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
@@ -174,31 +67,31 @@ Item {
|
|||||||
Button {
|
Button {
|
||||||
text: qsTr("Top")
|
text: qsTr("Top")
|
||||||
highlighted: app.currentPage === "top"
|
highlighted: app.currentPage === "top"
|
||||||
onClicked: loadTop()
|
onClicked: app.navigateToTop()
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("DE")
|
text: qsTr("DE")
|
||||||
highlighted: app.currentPage === "german"
|
highlighted: app.currentPage === "german"
|
||||||
onClicked: loadGerman()
|
onClicked: app.navigateToGerman()
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("NL")
|
text: qsTr("NL")
|
||||||
highlighted: app.currentPage === "dutch"
|
highlighted: app.currentPage === "dutch"
|
||||||
onClicked: loadDutch()
|
onClicked: app.navigateToDutch()
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("Favorites")
|
text: qsTr("Favorites")
|
||||||
highlighted: app.currentPage === "favorites"
|
highlighted: app.currentPage === "favorites"
|
||||||
onClicked: showFavorites()
|
onClicked: app.navigateToFavorites()
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("History")
|
text: qsTr("History")
|
||||||
highlighted: app.currentPage === "history"
|
highlighted: app.currentPage === "history"
|
||||||
onClicked: showHistory()
|
onClicked: app.navigateToHistory()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,23 +12,34 @@ Rectangle {
|
|||||||
|
|
||||||
required property var app
|
required property var app
|
||||||
|
|
||||||
implicitHeight: 108
|
implicitHeight: 76
|
||||||
radius: 12
|
radius: 0
|
||||||
color: BearTheme.panel
|
color: BearTheme.panel
|
||||||
border.color: BearTheme.cardBorder
|
border.color: "transparent"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
height: 1
|
||||||
|
color: BearTheme.cardBorder
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 10
|
anchors.leftMargin: 12
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
anchors.topMargin: 9
|
||||||
|
anchors.bottomMargin: 9
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.preferredWidth: 88
|
Layout.preferredWidth: 56
|
||||||
Layout.preferredHeight: 88
|
Layout.preferredHeight: 56
|
||||||
radius: 8
|
radius: 6
|
||||||
color: "#182637"
|
color: "#34353b"
|
||||||
clip: true
|
clip: true
|
||||||
border.color: BearTheme.cardBorder
|
border.color: "transparent"
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: coverImage
|
id: coverImage
|
||||||
@@ -58,19 +69,15 @@ Rectangle {
|
|||||||
&& app.backend.currentStation.favicon.startsWith("https://")) {
|
&& app.backend.currentStation.favicon.startsWith("https://")) {
|
||||||
return 8
|
return 8
|
||||||
}
|
}
|
||||||
return 16
|
return 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.alignment: Qt.AlignVCenter
|
||||||
spacing: 6
|
spacing: 3
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -78,71 +85,30 @@ Rectangle {
|
|||||||
? (app.backend.player.currentStationName || qsTr("No station selected"))
|
? (app.backend.player.currentStationName || qsTr("No station selected"))
|
||||||
: qsTr("No station selected")
|
: qsTr("No station selected")
|
||||||
color: BearTheme.textMain
|
color: BearTheme.textMain
|
||||||
font.pixelSize: 16
|
font.pixelSize: 14
|
||||||
font.bold: true
|
font.bold: true
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: app.backend && app.backend.currentStation && app.backend.currentStation.codec
|
|
||||||
&& app.backend.currentStation.codec !== "unknown"
|
|
||||||
&& app.backend.currentStation.codec !== ""
|
|
||||||
height: 18
|
|
||||||
width: codecLabel.implicitWidth + 12
|
|
||||||
radius: 4
|
|
||||||
color: "transparent"
|
|
||||||
border.color: BearTheme.accent
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: codecLabel
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: (app.backend && app.backend.currentStation && app.backend.currentStation.codec)
|
|
||||||
? app.backend.currentStation.codec.toUpperCase() : ""
|
|
||||||
color: BearTheme.accent
|
|
||||||
font.pixelSize: 9
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: app.backend && app.backend.currentStation && app.backend.currentStation.bitrate > 0
|
|
||||||
height: 18
|
|
||||||
width: bitrateLabel.implicitWidth + 12
|
|
||||||
radius: 4
|
|
||||||
color: BearTheme.accent
|
|
||||||
border.color: BearTheme.accent
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: bitrateLabel
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: (app.backend && app.backend.currentStation)
|
|
||||||
? app.backend.currentStation.bitrate + " kbps" : ""
|
|
||||||
color: "#ffffff"
|
|
||||||
font.pixelSize: 9
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: app.backend && app.backend.player && app.backend.player.currentNowPlaying.length > 0
|
text: app.backend && app.backend.player && app.backend.player.currentNowPlaying.length > 0
|
||||||
? (qsTr("Now playing: ") + app.backend.player.currentNowPlaying)
|
? (qsTr("Now playing: ") + app.backend.player.currentNowPlaying)
|
||||||
: qsTr("Now playing: No track info")
|
: qsTr("Live radio")
|
||||||
color: BearTheme.textMuted
|
color: BearTheme.textMuted
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.alignment: Qt.AlignVCenter
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: "⏮"
|
text: "⏮"
|
||||||
Layout.preferredWidth: 44
|
Layout.preferredWidth: 42
|
||||||
|
Layout.preferredHeight: 30
|
||||||
enabled: app.backend ? app.backend.hasPreviousStation() : false
|
enabled: app.backend ? app.backend.hasPreviousStation() : false
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (app.backend) {
|
if (app.backend) {
|
||||||
@@ -153,7 +119,8 @@ Rectangle {
|
|||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: app.backend && app.backend.player && app.backend.player.playing ? "⏸" : "▶"
|
text: app.backend && app.backend.player && app.backend.player.playing ? "⏸" : "▶"
|
||||||
Layout.preferredWidth: 44
|
Layout.preferredWidth: 42
|
||||||
|
Layout.preferredHeight: 30
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (app.backend && app.backend.player) {
|
if (app.backend && app.backend.player) {
|
||||||
app.backend.player.togglePlayPause()
|
app.backend.player.togglePlayPause()
|
||||||
@@ -163,7 +130,8 @@ Rectangle {
|
|||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: "⏹"
|
text: "⏹"
|
||||||
Layout.preferredWidth: 44
|
Layout.preferredWidth: 42
|
||||||
|
Layout.preferredHeight: 30
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (app.backend && app.backend.player) {
|
if (app.backend && app.backend.player) {
|
||||||
app.backend.player.stop()
|
app.backend.player.stop()
|
||||||
@@ -173,7 +141,8 @@ Rectangle {
|
|||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: "⏭"
|
text: "⏭"
|
||||||
Layout.preferredWidth: 44
|
Layout.preferredWidth: 42
|
||||||
|
Layout.preferredHeight: 30
|
||||||
enabled: app.backend ? app.backend.hasNextStation() : false
|
enabled: app.backend ? app.backend.hasNextStation() : false
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (app.backend) {
|
if (app.backend) {
|
||||||
@@ -185,7 +154,8 @@ Rectangle {
|
|||||||
Button {
|
Button {
|
||||||
id: muteButton
|
id: muteButton
|
||||||
text: (app.backend && app.backend.player && app.backend.player.volume > 0) ? "🔊" : "🔇"
|
text: (app.backend && app.backend.player && app.backend.player.volume > 0) ? "🔊" : "🔇"
|
||||||
Layout.preferredWidth: 44
|
Layout.preferredWidth: 36
|
||||||
|
Layout.preferredHeight: 30
|
||||||
property real lastVolume: 0.5
|
property real lastVolume: 0.5
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
@@ -205,7 +175,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Slider {
|
Slider {
|
||||||
Layout.fillWidth: true
|
Layout.preferredWidth: app.width < 1180 ? 115 : 160
|
||||||
from: 0
|
from: 0
|
||||||
to: 1
|
to: 1
|
||||||
value: app.backend && app.backend.player ? app.backend.player.volume : 0.5
|
value: app.backend && app.backend.player ? app.backend.player.volume : 0.5
|
||||||
@@ -218,4 +188,3 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@@ -22,6 +22,7 @@ ColumnLayout {
|
|||||||
app.backend.loadByTag(tag)
|
app.backend.loadByTag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load country by country code
|
||||||
function loadCountry(code, page) {
|
function loadCountry(code, page) {
|
||||||
if (!app.backend) return
|
if (!app.backend) return
|
||||||
app.currentPage = page
|
app.currentPage = page
|
||||||
@@ -40,72 +41,124 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
Flow {
|
||||||
visible: !root.compactMode
|
visible: !root.compactMode
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 6
|
Layout.preferredHeight: childrenRect.height
|
||||||
|
width: parent.width
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
// Left: Schnellzugriff filters
|
||||||
Label {
|
Label {
|
||||||
text: qsTr("Genre:")
|
text: qsTr("Quick access")
|
||||||
color: BearTheme.textMuted
|
color: BearTheme.textMuted
|
||||||
|
font.bold: true
|
||||||
font.pixelSize: 11
|
font.pixelSize: 11
|
||||||
rightPadding: 10
|
width: 86
|
||||||
|
height: 30
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
width: 72
|
||||||
|
height: 30
|
||||||
text: qsTr("Rock")
|
text: qsTr("Rock")
|
||||||
highlighted: app.activeQuickFilter === "tag:rock"
|
highlighted: app.activeQuickFilter === "tag:rock"
|
||||||
onClicked: loadTag("rock", "genre-rock")
|
onClicked: loadTag("rock", "genre-rock")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
width: 72
|
||||||
|
height: 30
|
||||||
text: qsTr("News")
|
text: qsTr("News")
|
||||||
highlighted: app.activeQuickFilter === "tag:news"
|
highlighted: app.activeQuickFilter === "tag:news"
|
||||||
onClicked: loadTag("news", "genre-news")
|
onClicked: loadTag("news", "genre-news")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
width: 72
|
||||||
|
height: 30
|
||||||
text: qsTr("Jazz")
|
text: qsTr("Jazz")
|
||||||
highlighted: app.activeQuickFilter === "tag:jazz"
|
highlighted: app.activeQuickFilter === "tag:jazz"
|
||||||
onClicked: loadTag("jazz", "genre-jazz")
|
onClicked: loadTag("jazz", "genre-jazz")
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.preferredWidth: 20 }
|
Rectangle {
|
||||||
|
width: 1
|
||||||
Label {
|
height: 18
|
||||||
text: qsTr("Country:")
|
color: BearTheme.cardBorder
|
||||||
color: BearTheme.textMuted
|
opacity: 0.6
|
||||||
font.pixelSize: 11
|
|
||||||
rightPadding: 10
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
width: 62
|
||||||
|
height: 30
|
||||||
text: qsTr("US")
|
text: qsTr("US")
|
||||||
highlighted: app.activeQuickFilter === "cc:US"
|
highlighted: app.activeQuickFilter === "cc:US"
|
||||||
onClicked: loadCountry("US", "country-us")
|
onClicked: loadCountry("US", "country-us")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("UK")
|
width: 62
|
||||||
|
height: 30
|
||||||
|
text: qsTr("GB")
|
||||||
highlighted: app.activeQuickFilter === "cc:GB"
|
highlighted: app.activeQuickFilter === "cc:GB"
|
||||||
onClicked: loadCountry("GB", "country-gb")
|
onClicked: loadCountry("GB", "country-gb")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
width: 62
|
||||||
|
height: 30
|
||||||
text: qsTr("FR")
|
text: qsTr("FR")
|
||||||
highlighted: app.activeQuickFilter === "cc:FR"
|
highlighted: app.activeQuickFilter === "cc:FR"
|
||||||
onClicked: loadCountry("FR", "country-fr")
|
onClicked: loadCountry("FR", "country-fr")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Sort")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 11
|
||||||
|
width: 76
|
||||||
|
height: 30
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("WORLD")
|
width: 78
|
||||||
highlighted: app.activeQuickFilter === "world"
|
height: 30
|
||||||
onClicked: openWorld()
|
text: qsTr("Name")
|
||||||
|
onClicked: {
|
||||||
|
if (app.backend && app.currentPage !== "favorites") {
|
||||||
|
app.backend.sortStations("name")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
Button {
|
||||||
|
width: 78
|
||||||
|
height: 30
|
||||||
|
text: qsTr("Bitrate")
|
||||||
|
onClicked: {
|
||||||
|
if (app.backend && app.currentPage !== "favorites") {
|
||||||
|
app.backend.sortStations("bitrate")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
width: 78
|
||||||
|
height: 30
|
||||||
|
text: qsTr("Votes")
|
||||||
|
onClicked: {
|
||||||
|
if (app.backend && app.currentPage !== "favorites") {
|
||||||
|
app.backend.sortStations("votes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact Mode (Mobile/Narrow) layout
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
visible: root.compactMode
|
visible: root.compactMode
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|||||||
@@ -21,12 +21,13 @@ ColumnLayout {
|
|||||||
RowLayout {
|
RowLayout {
|
||||||
visible: !root.compactMode
|
visible: !root.compactMode
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 8
|
spacing: 10
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
id: searchField
|
id: searchField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
placeholderText: qsTr("Search stations (name, genre, country)")
|
Layout.minimumWidth: 180
|
||||||
|
placeholderText: qsTr("Search stations (name, genre, country)...")
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (app.backend) {
|
if (app.backend) {
|
||||||
app.backend.filterQuery = text
|
app.backend.filterQuery = text
|
||||||
@@ -40,9 +41,14 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Right Search and Clear buttons
|
||||||
|
RowLayout {
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("Search")
|
text: root.app.width < 1180 ? "🔍" : "🔍 " + qsTr("Search")
|
||||||
highlighted: true
|
highlighted: true
|
||||||
|
Layout.preferredWidth: root.app.width < 1180 ? 44 : 88
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (searchField.text.length < 2 || !app.backend) return
|
if (searchField.text.length < 2 || !app.backend) return
|
||||||
app.currentPage = "search"
|
app.currentPage = "search"
|
||||||
@@ -51,33 +57,19 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("Sort A-Z")
|
text: root.app.width < 1180 ? "✕" : "✕ " + qsTr("Clear")
|
||||||
|
Layout.preferredWidth: root.app.width < 1180 ? 44 : 82
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (app.backend && app.currentPage !== "favorites") {
|
searchField.text = ""
|
||||||
app.backend.sortStations("name")
|
if (app.backend) {
|
||||||
|
app.backend.filterQuery = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("Sort Bitrate")
|
|
||||||
onClicked: {
|
|
||||||
if (app.backend && app.currentPage !== "favorites") {
|
|
||||||
app.backend.sortStations("bitrate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("Sort Votes")
|
|
||||||
onClicked: {
|
|
||||||
if (app.backend && app.currentPage !== "favorites") {
|
|
||||||
app.backend.sortStations("votes")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compact Mode (Mobile/Narrow) Search Layout
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
visible: root.compactMode
|
visible: root.compactMode
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -108,59 +100,27 @@ ColumnLayout {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("Search")
|
text: "🔍 " + qsTr("Search")
|
||||||
highlighted: true
|
highlighted: true
|
||||||
|
Layout.fillWidth: true
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (compactSearchField.text.length < 2 || !app.backend) return
|
if (searchField.text.length < 2 || !app.backend) return
|
||||||
app.currentPage = "search"
|
app.currentPage = "search"
|
||||||
app.backend.searchStations(compactSearchField.text)
|
app.backend.searchStations(searchField.text)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Flow {
|
Button {
|
||||||
|
text: "✕ " + qsTr("Clear")
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
width: parent.width
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "A-Z"
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (app.backend && app.currentPage !== "favorites") {
|
searchField.text = ""
|
||||||
app.backend.sortStations("name")
|
if (app.backend) {
|
||||||
}
|
app.backend.filterQuery = ""
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "kb"
|
|
||||||
onClicked: {
|
|
||||||
if (app.backend && app.currentPage !== "favorites") {
|
|
||||||
app.backend.sortStations("bitrate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "❤"
|
|
||||||
onClicked: {
|
|
||||||
if (app.backend && app.currentPage !== "favorites") {
|
|
||||||
app.backend.sortStations("votes")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: qsTr("Tip: you are not limited to DE/NL. Search worldwide by country, genre, or station name.")
|
|
||||||
color: BearTheme.textMuted
|
|
||||||
font.pixelSize: 11
|
|
||||||
wrapMode: root.compactMode ? Text.WordWrap : Text.NoWrap
|
|
||||||
elide: root.compactMode ? Text.ElideNone : Text.ElideRight
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
59
src/qml/components/SidebarButton.qml
Normal file
59
src/qml/components/SidebarButton.qml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: btn
|
||||||
|
|
||||||
|
property string text: ""
|
||||||
|
property string iconText: ""
|
||||||
|
property bool isActive: false
|
||||||
|
signal clicked()
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
radius: 6
|
||||||
|
|
||||||
|
color: isActive
|
||||||
|
? "#4b4b4f"
|
||||||
|
: (mouseArea.containsMouse ? "#36373d" : "transparent")
|
||||||
|
|
||||||
|
border.color: "transparent"
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: btn.clicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 9
|
||||||
|
anchors.rightMargin: 9
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: btn.iconText
|
||||||
|
font.pixelSize: 14
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: btn.text
|
||||||
|
color: btn.isActive ? BearTheme.textMain : (mouseArea.containsMouse ? BearTheme.textMain : BearTheme.textMuted)
|
||||||
|
font.bold: btn.isActive
|
||||||
|
font.pixelSize: 12
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
153
src/qml/components/SidebarNavigation.qml
Normal file
153
src/qml/components/SidebarNavigation.qml
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var app
|
||||||
|
required property bool compactMode
|
||||||
|
|
||||||
|
color: BearTheme.sidebar
|
||||||
|
border.color: "transparent"
|
||||||
|
border.width: 0
|
||||||
|
radius: 0
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 20
|
||||||
|
spacing: 14
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
Layout.bottomMargin: 8
|
||||||
|
|
||||||
|
Image {
|
||||||
|
Layout.preferredWidth: 34
|
||||||
|
Layout.preferredHeight: 34
|
||||||
|
source: "qrc:/assets/app/bearwave.png"
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
mipmap: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 28
|
||||||
|
source: "qrc:/assets/app/bearwave_line.png"
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
horizontalAlignment: Image.AlignLeft
|
||||||
|
smooth: true
|
||||||
|
mipmap: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Stations")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 10
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
SidebarButton {
|
||||||
|
text: qsTr("Top Global")
|
||||||
|
iconText: "🌍"
|
||||||
|
isActive: app.currentPage === "top"
|
||||||
|
onClicked: app.navigateToTop()
|
||||||
|
}
|
||||||
|
|
||||||
|
SidebarButton {
|
||||||
|
text: qsTr("Germany")
|
||||||
|
iconText: "🇩🇪"
|
||||||
|
isActive: app.currentPage === "german"
|
||||||
|
onClicked: app.navigateToGerman()
|
||||||
|
}
|
||||||
|
|
||||||
|
SidebarButton {
|
||||||
|
text: qsTr("Netherlands")
|
||||||
|
iconText: "🇳🇱"
|
||||||
|
isActive: app.currentPage === "dutch"
|
||||||
|
onClicked: app.navigateToDutch()
|
||||||
|
}
|
||||||
|
|
||||||
|
SidebarButton {
|
||||||
|
text: qsTr("Worldwide")
|
||||||
|
iconText: "🌐"
|
||||||
|
isActive: app.currentPage === "world"
|
||||||
|
onClicked: app.navigateToWorld()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Library")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 10
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 8
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
SidebarButton {
|
||||||
|
text: qsTr("Favorites")
|
||||||
|
iconText: "★"
|
||||||
|
isActive: app.currentPage === "favorites"
|
||||||
|
onClicked: app.navigateToFavorites()
|
||||||
|
}
|
||||||
|
|
||||||
|
SidebarButton {
|
||||||
|
text: qsTr("History")
|
||||||
|
iconText: "🕒"
|
||||||
|
isActive: app.currentPage === "history"
|
||||||
|
onClicked: app.navigateToHistory()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
SidebarButton {
|
||||||
|
text: qsTr("Add Station")
|
||||||
|
iconText: "➕"
|
||||||
|
isActive: false
|
||||||
|
onClicked: app.addDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
SidebarButton {
|
||||||
|
text: qsTr("About BearWave")
|
||||||
|
iconText: "ℹ️"
|
||||||
|
isActive: app.currentPage === "about"
|
||||||
|
onClicked: app.navigateToAbout()
|
||||||
|
}
|
||||||
|
|
||||||
|
SidebarButton {
|
||||||
|
visible: app.backend && app.backend.canResumeLastStation
|
||||||
|
text: qsTr("Resume")
|
||||||
|
iconText: "↺"
|
||||||
|
isActive: false
|
||||||
|
onClicked: {
|
||||||
|
if (app.backend) {
|
||||||
|
app.backend.resumeLastStation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,9 +16,9 @@ Rectangle {
|
|||||||
required property bool compactMode
|
required property bool compactMode
|
||||||
required property real listWidth
|
required property real listWidth
|
||||||
|
|
||||||
height: compactMode ? 72 : 78
|
height: 52
|
||||||
width: listWidth
|
width: listWidth
|
||||||
radius: 10
|
radius: 0
|
||||||
|
|
||||||
readonly property bool isCurrent: {
|
readonly property bool isCurrent: {
|
||||||
if (!app.backend || !modelData) return false
|
if (!app.backend || !modelData) return false
|
||||||
@@ -34,10 +34,24 @@ Rectangle {
|
|||||||
readonly property bool isPlaying: isCurrent && app.backend && app.backend.player && app.backend.player.playing
|
readonly property bool isPlaying: isCurrent && app.backend && app.backend.player && app.backend.player.playing
|
||||||
|
|
||||||
color: isCurrent
|
color: isCurrent
|
||||||
? (cardMouse.containsMouse ? "#1d3350" : "#16283e")
|
? "#4a4a4d"
|
||||||
: (cardMouse.containsMouse ? BearTheme.cardHover : BearTheme.card)
|
: (cardMouse.containsMouse ? "#2d2e34" : "transparent")
|
||||||
border.color: isCurrent ? BearTheme.accent : BearTheme.cardBorder
|
|
||||||
border.width: isCurrent ? 2 : 1
|
border.width: 0
|
||||||
|
|
||||||
|
readonly property string metadataText: {
|
||||||
|
if (!modelData) return ""
|
||||||
|
var parts = []
|
||||||
|
if (modelData.country) parts.push(modelData.country)
|
||||||
|
if (modelData.codec && modelData.codec !== "unknown" && modelData.codec !== "") parts.push(modelData.codec.toUpperCase())
|
||||||
|
if (modelData.bitrate > 0) parts.push(modelData.bitrate + " kbps")
|
||||||
|
if (modelData.votes > 0) {
|
||||||
|
parts.push(modelData.votes.toLocaleString(Qt.locale(), "f", 0) + " " + qsTr("votes"))
|
||||||
|
} else {
|
||||||
|
parts.push(qsTr("Stream"))
|
||||||
|
}
|
||||||
|
return parts.join(" • ")
|
||||||
|
}
|
||||||
|
|
||||||
function playStation() {
|
function playStation() {
|
||||||
if (!app.backend) return
|
if (!app.backend) return
|
||||||
@@ -56,6 +70,7 @@ Rectangle {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
app.selectedStation = root.modelData
|
||||||
if (!app.backend) return
|
if (!app.backend) return
|
||||||
if (root.isCurrent) {
|
if (root.isCurrent) {
|
||||||
app.backend.player.togglePlayPause()
|
app.backend.player.togglePlayPause()
|
||||||
@@ -65,22 +80,33 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
height: 1
|
||||||
|
color: BearTheme.cardBorder
|
||||||
|
opacity: 0.65
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 10
|
anchors.leftMargin: 8
|
||||||
|
anchors.rightMargin: 8
|
||||||
spacing: 10
|
spacing: 10
|
||||||
|
|
||||||
|
// Favicon Rect
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.preferredWidth: 44
|
Layout.preferredWidth: 34
|
||||||
Layout.preferredHeight: 44
|
Layout.preferredHeight: 34
|
||||||
radius: 8
|
radius: 5
|
||||||
color: "#123154"
|
color: "#34353b"
|
||||||
border.color: BearTheme.accent
|
clip: true
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: stationFavicon
|
id: stationFavicon
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 3
|
anchors.margins: 2
|
||||||
source: (root.modelData.favicon && root.modelData.favicon.startsWith("https://"))
|
source: (root.modelData.favicon && root.modelData.favicon.startsWith("https://"))
|
||||||
? root.modelData.favicon
|
? root.modelData.favicon
|
||||||
: ""
|
: ""
|
||||||
@@ -100,9 +126,11 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Info Column
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 2
|
spacing: 1
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -113,10 +141,11 @@ Rectangle {
|
|||||||
text: root.modelData.name
|
text: root.modelData.name
|
||||||
color: root.isCurrent ? BearTheme.accent : BearTheme.textMain
|
color: root.isCurrent ? BearTheme.accent : BearTheme.textMain
|
||||||
font.bold: true
|
font.bold: true
|
||||||
font.pixelSize: compactMode ? 13 : 14
|
font.pixelSize: 13
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tiny EQ Indicator
|
||||||
Row {
|
Row {
|
||||||
id: eqAnimation
|
id: eqAnimation
|
||||||
spacing: 2
|
spacing: 2
|
||||||
@@ -126,32 +155,26 @@ Rectangle {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: bar1
|
id: bar1
|
||||||
width: 2
|
width: 2
|
||||||
height: 12
|
height: 10
|
||||||
color: BearTheme.accent
|
color: BearTheme.playingAccent
|
||||||
radius: 1
|
radius: 1
|
||||||
Behavior on height {
|
Behavior on height { NumberAnimation { duration: 120 } }
|
||||||
NumberAnimation { duration: 120 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: bar2
|
id: bar2
|
||||||
width: 2
|
width: 2
|
||||||
height: 12
|
height: 10
|
||||||
color: BearTheme.accent
|
color: BearTheme.playingAccent
|
||||||
radius: 1
|
radius: 1
|
||||||
Behavior on height {
|
Behavior on height { NumberAnimation { duration: 120 } }
|
||||||
NumberAnimation { duration: 120 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: bar3
|
id: bar3
|
||||||
width: 2
|
width: 2
|
||||||
height: 12
|
height: 10
|
||||||
color: BearTheme.accent
|
color: BearTheme.playingAccent
|
||||||
radius: 1
|
radius: 1
|
||||||
Behavior on height {
|
Behavior on height { NumberAnimation { duration: 120 } }
|
||||||
NumberAnimation { duration: 120 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -159,17 +182,17 @@ Rectangle {
|
|||||||
running: root.isPlaying
|
running: root.isPlaying
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
bar1.height = Math.floor(Math.random() * 11) + 3
|
bar1.height = Math.floor(Math.random() * 9) + 2
|
||||||
bar2.height = Math.floor(Math.random() * 11) + 3
|
bar2.height = Math.floor(Math.random() * 9) + 2
|
||||||
bar3.height = Math.floor(Math.random() * 11) + 3
|
bar3.height = Math.floor(Math.random() * 9) + 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
bar1.height = 12
|
bar1.height = 10
|
||||||
bar2.height = 12
|
bar2.height = 10
|
||||||
bar3.height = 12
|
bar3.height = 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,21 +200,20 @@ Rectangle {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: root.modelData.country + " • "
|
text: root.metadataText
|
||||||
+ (root.modelData.codec && root.modelData.codec !== "unknown" && root.modelData.codec !== ""
|
|
||||||
? root.modelData.codec.toUpperCase() + " • " : "")
|
|
||||||
+ (root.modelData.bitrate > 0 ? root.modelData.bitrate + " kbps" : qsTr("Stream"))
|
|
||||||
color: BearTheme.textMuted
|
color: BearTheme.textMuted
|
||||||
font.pixelSize: 11
|
font.pixelSize: 11
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Edit Button (Custom User Stations Only)
|
||||||
Button {
|
Button {
|
||||||
visible: !root.modelData.uuid
|
visible: !root.modelData.uuid
|
||||||
Layout.preferredWidth: compactMode ? 34 : 40
|
Layout.preferredWidth: 32
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: 32
|
||||||
text: "✏"
|
text: "✏"
|
||||||
|
flat: true
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: qsTr("Edit Station")
|
ToolTip.text: qsTr("Edit Station")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
@@ -200,10 +222,13 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Favorite Button
|
||||||
Button {
|
Button {
|
||||||
Layout.preferredWidth: compactMode ? 34 : 40
|
Layout.preferredWidth: 32
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: 32
|
||||||
text: root.modelData.isFavorite ? "★" : "☆"
|
text: root.modelData.isFavorite ? "★" : "☆"
|
||||||
|
flat: true
|
||||||
|
font.pixelSize: 14
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!app.backend) return
|
if (!app.backend) return
|
||||||
app.backend.toggleFavoriteById(root.modelData.uuid, root.modelData.urlResolved)
|
app.backend.toggleFavoriteById(root.modelData.uuid, root.modelData.urlResolved)
|
||||||
@@ -211,10 +236,13 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Play/Pause Button
|
||||||
Button {
|
Button {
|
||||||
Layout.preferredWidth: compactMode ? 34 : 40
|
Layout.preferredWidth: 32
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: 32
|
||||||
text: (root.isCurrent && app.backend && app.backend.player && app.backend.player.playing) ? "⏸" : "▶"
|
text: (root.isCurrent && app.backend && app.backend.player && app.backend.player.playing) ? "⏸" : "▶"
|
||||||
|
flat: true
|
||||||
|
font.pixelSize: 13
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!app.backend) return
|
if (!app.backend) return
|
||||||
if (root.isCurrent) {
|
if (root.isCurrent) {
|
||||||
|
|||||||
359
src/qml/components/StationDetailsPanel.qml
Normal file
359
src/qml/components/StationDetailsPanel.qml
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var app
|
||||||
|
|
||||||
|
color: BearTheme.panel
|
||||||
|
border.color: "transparent"
|
||||||
|
border.width: 0
|
||||||
|
radius: 0
|
||||||
|
|
||||||
|
readonly property var activeStation: app.selectedStation || (app.backend ? app.backend.currentStation : null)
|
||||||
|
readonly property bool isCurrentPlaying: {
|
||||||
|
if (!activeStation || !app.backend) return false
|
||||||
|
var currentUuid = app.backend.currentStationUuid
|
||||||
|
var currentUrl = app.backend.currentStationUrl
|
||||||
|
var activeUuid = activeStation.uuid || ""
|
||||||
|
var activeUrl = activeStation.urlResolved || activeStation.url || ""
|
||||||
|
if (currentUuid !== "" && activeUuid !== "") {
|
||||||
|
return currentUuid === activeUuid
|
||||||
|
}
|
||||||
|
return currentUrl !== "" && currentUrl === activeUrl
|
||||||
|
}
|
||||||
|
readonly property bool isPlaying: isCurrentPlaying && app.backend && app.backend.player && app.backend.player.playing
|
||||||
|
|
||||||
|
// Helper text edit to facilitate clipboard copying
|
||||||
|
TextEdit {
|
||||||
|
id: clipboardHelper
|
||||||
|
visible: false
|
||||||
|
selectByMouse: true
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard(text) {
|
||||||
|
clipboardHelper.text = text
|
||||||
|
clipboardHelper.selectAll()
|
||||||
|
clipboardHelper.copy()
|
||||||
|
app.toast(qsTr("Stream URL copied!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 16
|
||||||
|
spacing: 10
|
||||||
|
visible: root.activeStation !== null
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
id: detailsScroll
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
width: detailsScroll.availableWidth
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
// Cover Art / Favicon
|
||||||
|
Rectangle {
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
Layout.preferredWidth: 72
|
||||||
|
Layout.preferredHeight: 72
|
||||||
|
radius: 8
|
||||||
|
color: "#34353b"
|
||||||
|
border.color: "transparent"
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: detailFavicon
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: {
|
||||||
|
if (app.backend && app.backend.player && app.backend.player.currentCoverArtUrl && root.isCurrentPlaying) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return activeStation && activeStation.favicon && activeStation.favicon.startsWith("https://") ? 8 : 16
|
||||||
|
}
|
||||||
|
source: {
|
||||||
|
if (!root.activeStation) return ""
|
||||||
|
if (root.isCurrentPlaying && app.backend && app.backend.player && app.backend.player.currentCoverArtUrl && app.backend.player.currentCoverArtUrl !== "") {
|
||||||
|
return app.backend.player.currentCoverArtUrl
|
||||||
|
}
|
||||||
|
return (root.activeStation.favicon && root.activeStation.favicon.startsWith("https://")) ? root.activeStation.favicon : ""
|
||||||
|
}
|
||||||
|
fillMode: (root.isCurrentPlaying && app.backend && app.backend.player && app.backend.player.currentCoverArtUrl && app.backend.player.currentCoverArtUrl !== "") ? Image.PreserveAspectCrop : Image.PreserveAspectFit
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
visible: source !== "" && status === Image.Ready
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 16
|
||||||
|
source: "qrc:/assets/app/bearwave.png"
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
visible: !detailFavicon.visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Station Name & Country
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 4
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: root.activeStation ? root.activeStation.name : ""
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 16
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: root.activeStation ? root.activeStation.country : ""
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.pixelSize: 13
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: root.isPlaying ? "⏸ " + qsTr("Pause") : "▶ " + qsTr("Play")
|
||||||
|
highlighted: true
|
||||||
|
width: 86
|
||||||
|
height: 28
|
||||||
|
font.pixelSize: 11
|
||||||
|
onClicked: {
|
||||||
|
if (!app.backend || !root.activeStation) return
|
||||||
|
if (root.isCurrentPlaying) {
|
||||||
|
app.backend.player.togglePlayPause()
|
||||||
|
} else {
|
||||||
|
// Find index or play by url
|
||||||
|
app.backend.playRecentByUuid(root.activeStation.uuid, root.activeStation.urlResolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: root.activeStation && root.activeStation.isFavorite ? "★ " + qsTr("Favorite") : "☆ " + qsTr("Add")
|
||||||
|
width: 104
|
||||||
|
height: 28
|
||||||
|
font.pixelSize: 11
|
||||||
|
onClicked: {
|
||||||
|
if (!app.backend || !root.activeStation) return
|
||||||
|
app.backend.toggleFavoriteById(root.activeStation.uuid, root.activeStation.urlResolved)
|
||||||
|
app.toast(root.activeStation.isFavorite ? qsTr("Removed from favorites") : qsTr("Added to favorites"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "📁 " + qsTr("Groups")
|
||||||
|
width: 92
|
||||||
|
height: 28
|
||||||
|
font.pixelSize: 11
|
||||||
|
onClicked: app.toast(qsTr("Groups coming soon"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
color: BearTheme.cardBorder
|
||||||
|
opacity: 0.6
|
||||||
|
}
|
||||||
|
|
||||||
|
// Technical Specifications
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Details")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
columns: 2
|
||||||
|
columnSpacing: 16
|
||||||
|
rowSpacing: 8
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Codec:")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: (root.activeStation && root.activeStation.codec && root.activeStation.codec !== "unknown") ? root.activeStation.codec.toUpperCase() : qsTr("Unknown")
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.pixelSize: 12
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Bitrate:")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: (root.activeStation && root.activeStation.bitrate > 0) ? root.activeStation.bitrate + " kbps" : qsTr("Unknown")
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.pixelSize: 12
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Votes:")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: root.activeStation ? "" + root.activeStation.votes : "0"
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.pixelSize: 12
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
color: BearTheme.cardBorder
|
||||||
|
opacity: 0.6
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags / Genres
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Tags")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: (root.activeStation && root.activeStation.tags) ? root.activeStation.tags.split(",") : []
|
||||||
|
delegate: Rectangle {
|
||||||
|
visible: modelData.trim().length > 0
|
||||||
|
height: tagText.implicitHeight + 8
|
||||||
|
width: tagText.implicitWidth + 12
|
||||||
|
radius: 4
|
||||||
|
color: BearTheme.card
|
||||||
|
border.color: BearTheme.cardBorder
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: tagText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: modelData.trim()
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.pixelSize: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
color: BearTheme.cardBorder
|
||||||
|
opacity: 0.6
|
||||||
|
}
|
||||||
|
|
||||||
|
// Links / URLs
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Links")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "🔗 " + qsTr("Open homepage")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
enabled: root.activeStation && root.activeStation.homepage && root.activeStation.homepage.startsWith("http")
|
||||||
|
onClicked: {
|
||||||
|
if (root.activeStation && root.activeStation.homepage) {
|
||||||
|
Qt.openUrlExternally(root.activeStation.homepage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "📋 " + qsTr("Copy stream URL")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
enabled: root.activeStation && (root.activeStation.urlResolved || root.activeStation.url)
|
||||||
|
onClicked: {
|
||||||
|
if (root.activeStation) {
|
||||||
|
var url = root.activeStation.urlResolved || root.activeStation.url
|
||||||
|
root.copyToClipboard(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty state if no station is active
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 12
|
||||||
|
visible: root.activeStation === null
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "📻"
|
||||||
|
font.pixelSize: 48
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("No station selected")
|
||||||
|
color: BearTheme.textMain
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 14
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Select a station from the list\nto view details.")
|
||||||
|
color: BearTheme.textMuted
|
||||||
|
font.pixelSize: 12
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ Item {
|
|||||||
ListView {
|
ListView {
|
||||||
id: stationList
|
id: stationList
|
||||||
width: stationScrollView.availableWidth
|
width: stationScrollView.availableWidth
|
||||||
spacing: 8
|
spacing: 0
|
||||||
model: app.activeModel()
|
model: app.activeModel()
|
||||||
visible: count > 0
|
visible: count > 0
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ StationCard 1.0 StationCard.qml
|
|||||||
WorldCategoryHeader 1.0 WorldCategoryHeader.qml
|
WorldCategoryHeader 1.0 WorldCategoryHeader.qml
|
||||||
WorldCategories 1.0 WorldCategories.qml
|
WorldCategories 1.0 WorldCategories.qml
|
||||||
PlayerBar 1.0 PlayerBar.qml
|
PlayerBar 1.0 PlayerBar.qml
|
||||||
AboutDialog 1.0 AboutDialog.qml
|
AboutPage 1.0 AboutPage.qml
|
||||||
AddStationDialog 1.0 AddStationDialog.qml
|
AddStationDialog 1.0 AddStationDialog.qml
|
||||||
EditStationDialog 1.0 EditStationDialog.qml
|
EditStationDialog 1.0 EditStationDialog.qml
|
||||||
LoadingOverlay 1.0 LoadingOverlay.qml
|
LoadingOverlay 1.0 LoadingOverlay.qml
|
||||||
ToastPopup 1.0 ToastPopup.qml
|
ToastPopup 1.0 ToastPopup.qml
|
||||||
StationListPanel 1.0 StationListPanel.qml
|
StationListPanel 1.0 StationListPanel.qml
|
||||||
|
SidebarNavigation 1.0 SidebarNavigation.qml
|
||||||
|
SidebarButton 1.0 SidebarButton.qml
|
||||||
|
StationDetailsPanel 1.0 StationDetailsPanel.qml
|
||||||
|
|||||||
@@ -5,15 +5,17 @@ pragma Singleton
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
readonly property color bgA: "#0f141b"
|
readonly property color bgA: "#1d1d22"
|
||||||
readonly property color bgB: "#131b25"
|
readonly property color bgB: "#202027"
|
||||||
readonly property color panel: "#182433"
|
readonly property color panel: "#22232a"
|
||||||
readonly property color card: "#1b2a3d"
|
readonly property color sidebar: "#2b2c33"
|
||||||
readonly property color cardHover: "#223654"
|
readonly property color card: "#2a2b31"
|
||||||
readonly property color cardBorder: "#2d4566"
|
readonly property color cardHover: "#33343b"
|
||||||
readonly property color accent: "#2bb0ff"
|
readonly property color cardBorder: "#3a3b43"
|
||||||
readonly property color textMain: "#eaf1fb"
|
readonly property color accent: "#d7d7df"
|
||||||
readonly property color textMuted: "#9eb1c9"
|
readonly property color playingAccent: "#0a84ff"
|
||||||
|
readonly property color textMain: "#f0f0f4"
|
||||||
|
readonly property color textMuted: "#a3a4ad"
|
||||||
readonly property color warn: "#ff8b8b"
|
readonly property color warn: "#ff8b8b"
|
||||||
|
|
||||||
readonly property var worldTags: [
|
readonly property var worldTags: [
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ void QrcResourcesTest::qmlModulesAreEmbedded()
|
|||||||
QVERIFY(QFile::exists(QStringLiteral(":/qml/theme/qmldir")));
|
QVERIFY(QFile::exists(QStringLiteral(":/qml/theme/qmldir")));
|
||||||
QVERIFY(QFile::exists(QStringLiteral(":/qml/components/qmldir")));
|
QVERIFY(QFile::exists(QStringLiteral(":/qml/components/qmldir")));
|
||||||
QVERIFY(QFile::exists(QStringLiteral(":/assets/app/bearwave.png")));
|
QVERIFY(QFile::exists(QStringLiteral(":/assets/app/bearwave.png")));
|
||||||
|
QVERIFY(QFile::exists(QStringLiteral(":/assets/legal/gpl-3.0.txt")));
|
||||||
}
|
}
|
||||||
|
|
||||||
QTEST_APPLESS_MAIN(QrcResourcesTest)
|
QTEST_APPLESS_MAIN(QrcResourcesTest)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,58 +1,175 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<!DOCTYPE TS>
|
|
||||||
<TS version="2.1" language="ru_RU">
|
<TS version="2.1" language="ru_RU">
|
||||||
<context>
|
<context>
|
||||||
<name>AboutDialog</name>
|
<name>AboutDialog</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/AboutDialog.qml" line="24"/>
|
<source>Version</source>
|
||||||
|
<translation type="vanished">Версия</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>build</source>
|
||||||
|
<translation type="vanished">сборка</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>BearWave</source>
|
||||||
|
<translation type="vanished">BearWave</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Internet Radio Player for KDE</source>
|
||||||
|
<translation type="vanished">Интернет радиоплеер для KDE</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Author: Sebastian Palencsár</source>
|
||||||
|
<translation type="vanished">Автор: Sebastian Palencsár</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open website</source>
|
||||||
|
<translation type="vanished">Перейти на веб-сайт</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open GitHub</source>
|
||||||
|
<translation type="vanished">Перейти на GitHub</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open LinkedIn</source>
|
||||||
|
<translation type="vanished">Перейти на LinkedIn</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>GNU GPLv3 License</source>
|
||||||
|
<translation type="vanished">Лицензия GNU GPLv3</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Copyright (c) 2026</source>
|
||||||
|
<translation type="vanished">Copyright (c) 2026</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>AboutPage</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="25" />
|
||||||
<source>Version</source>
|
<source>Version</source>
|
||||||
<translation>Версия</translation>
|
<translation>Версия</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/AboutDialog.qml" line="24"/>
|
<location filename="../src/qml/components/AboutPage.qml" line="25" />
|
||||||
<source>build</source>
|
<source>Build</source>
|
||||||
<translation>сборка</translation>
|
<translation>Сборка</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/AboutDialog.qml" line="53"/>
|
<location filename="../src/qml/components/AboutPage.qml" line="30" />
|
||||||
<source>BearWave</source>
|
<source>Qt 6</source>
|
||||||
<translation>BearWave</translation>
|
<translation>Qt 6</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/AboutDialog.qml" line="61"/>
|
<location filename="../src/qml/components/AboutPage.qml" line="31" />
|
||||||
<source>Internet Radio Player for KDE</source>
|
<source>Application framework and UI runtime: Core, DBus, Network, Quick, Quick Controls 2, Widgets, Multimedia, and LinguistTools.</source>
|
||||||
<translation>Интернет радиоплеер для KDE</translation>
|
<translation>Прикладной фреймворк и среда выполнения UI: Core, DBus, Network, Quick, Quick Controls 2, Widgets, Multimedia и LinguistTools.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/AboutDialog.qml" line="82"/>
|
<location filename="../src/qml/components/AboutPage.qml" line="32" />
|
||||||
<source>Author: Sebastian Palencsár</source>
|
<source>Subject to the upstream licenses of the installed Qt distribution.</source>
|
||||||
<translation>Автор: Sebastian Palencsár</translation>
|
<translation>Действуют лицензии upstream-версии установленного дистрибутива Qt.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/AboutDialog.qml" line="97"/>
|
<location filename="../src/qml/components/AboutPage.qml" line="35" />
|
||||||
|
<source>Qt Multimedia backend</source>
|
||||||
|
<translation>Мультимедийный бэкенд Qt</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="36" />
|
||||||
|
<source>Audio playback runs through Qt Multimedia and the backend available on the system, such as GStreamer or FFmpeg.</source>
|
||||||
|
<translation>Воспроизведение аудио выполняется через Qt Multimedia и доступный в системе бэкенд, например GStreamer или FFmpeg.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="37" />
|
||||||
|
<source>Backend components are provided by the operating system or Qt build and retain their own licenses.</source>
|
||||||
|
<translation>Компоненты бэкенда предоставляются операционной системой или сборкой Qt и сохраняют свои собственные лицензии.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="40" />
|
||||||
|
<source>Radio Browser API</source>
|
||||||
|
<translation>Radio Browser API</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="41" />
|
||||||
|
<source>Public station directory API for station data and stream discovery. BearWave does not bundle any Radio Browser server code.</source>
|
||||||
|
<translation>Публичный API каталога станций для данных о станциях и обнаружения потоков. BearWave не включает серверный код Radio Browser.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="42" />
|
||||||
|
<source>External service or API; subject to the upstream project and service terms.</source>
|
||||||
|
<translation>Внешний сервис или API; действуют условия upstream-проекта и сервиса.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="45" />
|
||||||
|
<source>freedesktop.org integrations</source>
|
||||||
|
<translation>Интеграции freedesktop.org</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="46" />
|
||||||
|
<source>D-Bus, MPRIS, desktop notifications, desktop entry, and AppStream metadata.</source>
|
||||||
|
<translation>D-Bus, MPRIS, уведомления рабочего стола, desktop-файл и метаданные AppStream.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="47" />
|
||||||
|
<source>Uses standards and interfaces for desktop integration; no third-party implementation is bundled.</source>
|
||||||
|
<translation>Используются стандарты и интерфейсы для интеграции с рабочим столом; сторонние реализации не включаются.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="95" />
|
||||||
|
<source>Internet radio player for KDE</source>
|
||||||
|
<translation>Интернет-радиоплеер для KDE</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="126" />
|
||||||
|
<source>Author</source>
|
||||||
|
<translation>Автор</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="138" />
|
||||||
|
<source>Sebastian Palencsár</source>
|
||||||
|
<translation>Себастьян Паленчар</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="150" />
|
||||||
<source>Open website</source>
|
<source>Open website</source>
|
||||||
<translation>Перейти на веб-сайт</translation>
|
<translation>Перейти на веб-сайт</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/AboutDialog.qml" line="121"/>
|
<location filename="../src/qml/components/AboutPage.qml" line="168" />
|
||||||
<source>Open GitHub</source>
|
<source>Open GitHub</source>
|
||||||
<translation>Перейти на GitHub</translation>
|
<translation>Перейти на GitHub</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/AboutDialog.qml" line="145"/>
|
<location filename="../src/qml/components/AboutPage.qml" line="186" />
|
||||||
<source>Open LinkedIn</source>
|
<source>Open LinkedIn</source>
|
||||||
<translation>Перейти на LinkedIn</translation>
|
<translation>Перейти на LinkedIn</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/AboutDialog.qml" line="175"/>
|
<location filename="../src/qml/components/AboutPage.qml" line="216" />
|
||||||
|
<source>Technologies and third-party components</source>
|
||||||
|
<translation>Технологии и сторонние компоненты</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="224" />
|
||||||
|
<source>BearWave uses the following frameworks, services, and desktop standards. These components are not relicensed by BearWave.</source>
|
||||||
|
<translation>BearWave использует следующие фреймворки, сервисы и стандарты рабочего стола. Эти компоненты не перелицензируются BearWave.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="298" />
|
||||||
<source>GNU GPLv3 License</source>
|
<source>GNU GPLv3 License</source>
|
||||||
<translation>Лицензия GNU GPLv3</translation>
|
<translation>Лицензия GNU GPLv3</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/AboutDialog.qml" line="180"/>
|
<location filename="../src/qml/components/AboutPage.qml" line="305" />
|
||||||
<source>Copyright (c) 2026</source>
|
<source>Copyright (c) 2026</source>
|
||||||
<translation>Copyright (c) 2026</translation>
|
<translation>Copyright (c) 2026</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/AboutPage.qml" line="334" />
|
||||||
|
<source>License text could not be loaded.</source>
|
||||||
|
<translation>Не удалось загрузить текст лицензии.</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>AddStationDialog</name>
|
<name>AddStationDialog</name>
|
||||||
@@ -85,62 +202,62 @@
|
|||||||
<context>
|
<context>
|
||||||
<name>BearTheme</name>
|
<name>BearTheme</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="20"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="22" />
|
||||||
<source>Pop</source>
|
<source>Pop</source>
|
||||||
<translation>Поп</translation>
|
<translation>Поп</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="21"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="23" />
|
||||||
<source>Rock</source>
|
<source>Rock</source>
|
||||||
<translation>Рок</translation>
|
<translation>Рок</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="22"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="24" />
|
||||||
<source>Electronic</source>
|
<source>Electronic</source>
|
||||||
<translation>Электронная</translation>
|
<translation>Электронная</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="23"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="25" />
|
||||||
<source>Classical</source>
|
<source>Classical</source>
|
||||||
<translation>Классическая</translation>
|
<translation>Классическая</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="24"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="26" />
|
||||||
<source>Jazz</source>
|
<source>Jazz</source>
|
||||||
<translation>Джаз</translation>
|
<translation>Джаз</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="25"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="27" />
|
||||||
<source>Metal</source>
|
<source>Metal</source>
|
||||||
<translation>Метал</translation>
|
<translation>Метал</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="26"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="28" />
|
||||||
<source>Hip Hop</source>
|
<source>Hip Hop</source>
|
||||||
<translation>Хип-хоп</translation>
|
<translation>Хип-хоп</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="27"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="29" />
|
||||||
<source>Chillout</source>
|
<source>Chillout</source>
|
||||||
<translation>Успокаивающая</translation>
|
<translation>Успокаивающая</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="28"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="30" />
|
||||||
<source>News / Talk</source>
|
<source>News / Talk</source>
|
||||||
<translation>Новости / Разговоры</translation>
|
<translation>Новости / Разговоры</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="29"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="31" />
|
||||||
<source>Soundtracks</source>
|
<source>Soundtracks</source>
|
||||||
<translation>Саундтреки</translation>
|
<translation>Саундтреки</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="30"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="32" />
|
||||||
<source>Ambient</source>
|
<source>Ambient</source>
|
||||||
<translation>Атмосфера</translation>
|
<translation>Атмосфера</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/theme/BearTheme.qml" line="31"/>
|
<location filename="../src/qml/theme/BearTheme.qml" line="33" />
|
||||||
<source>Blues / Soul</source>
|
<source>Blues / Soul</source>
|
||||||
<translation>Блюз / Соул</translation>
|
<translation>Блюз / Соул</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -181,50 +298,42 @@
|
|||||||
<context>
|
<context>
|
||||||
<name>HeaderNavigation</name>
|
<name>HeaderNavigation</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="74"/>
|
<location filename="../src/qml/components/HeaderNavigation.qml" line="68" />
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="175"/>
|
|
||||||
<source>Top</source>
|
<source>Top</source>
|
||||||
<translation>Топ</translation>
|
<translation>Топ</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="80"/>
|
<location filename="../src/qml/components/HeaderNavigation.qml" line="74" />
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="181"/>
|
|
||||||
<source>DE</source>
|
<source>DE</source>
|
||||||
<translation>DE</translation>
|
<translation>DE</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="86"/>
|
<location filename="../src/qml/components/HeaderNavigation.qml" line="80" />
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="187"/>
|
|
||||||
<source>NL</source>
|
<source>NL</source>
|
||||||
<translation>NL</translation>
|
<translation>NL</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="92"/>
|
<location filename="../src/qml/components/HeaderNavigation.qml" line="86" />
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="193"/>
|
|
||||||
<source>Favorites</source>
|
<source>Favorites</source>
|
||||||
<translation>Избранные</translation>
|
<translation>Избранные</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="98"/>
|
<location filename="../src/qml/components/HeaderNavigation.qml" line="92" />
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="199"/>
|
|
||||||
<source>History</source>
|
<source>History</source>
|
||||||
<translation>История</translation>
|
<translation>История</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="106"/>
|
|
||||||
<source>Manual +</source>
|
<source>Manual +</source>
|
||||||
<translation>Вручную +</translation>
|
<translation type="vanished">Вручную +</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="111"/>
|
<location filename="../src/qml/components/HeaderNavigation.qml" line="46" />
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="154"/>
|
|
||||||
<source>About</source>
|
<source>About</source>
|
||||||
<translation>О BearWave</translation>
|
<translation>О BearWave</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/HeaderNavigation.qml" line="117"/>
|
|
||||||
<source>Resume</source>
|
<source>Resume</source>
|
||||||
<translation>Продолжить</translation>
|
<translation type="vanished">Продолжить</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@@ -243,12 +352,12 @@
|
|||||||
<translation>BearWave</translation>
|
<translation>BearWave</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/Main.qml" line="168"/>
|
<location filename="../src/qml/Main.qml" line="245" />
|
||||||
<source>Error: </source>
|
<source>Error: </source>
|
||||||
<translation>Ошибка: </translation>
|
<translation>Ошибка: </translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/Main.qml" line="59"/>
|
<location filename="../src/qml/Main.qml" line="103" />
|
||||||
<source>Top Global</source>
|
<source>Top Global</source>
|
||||||
<translation>Всемирный топ</translation>
|
<translation>Всемирный топ</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -256,36 +365,39 @@
|
|||||||
<context>
|
<context>
|
||||||
<name>NotificationManager</name>
|
<name>NotificationManager</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/notificationmanager.cpp" line="214"/>
|
|
||||||
<source>Station: %1</source>
|
<source>Station: %1</source>
|
||||||
<translation>Станция: %1</translation>
|
<translation type="vanished">Станция: %1</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>PlayerBar</name>
|
<name>PlayerBar</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/PlayerBar.qml" line="78"/>
|
<location filename="../src/qml/components/PlayerBar.qml" line="85" />
|
||||||
<location filename="../src/qml/components/PlayerBar.qml" line="79"/>
|
<location filename="../src/qml/components/PlayerBar.qml" line="86" />
|
||||||
<source>No station selected</source>
|
<source>No station selected</source>
|
||||||
<translation>Нет выбранной станции</translation>
|
<translation>Нет выбранной станции</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/PlayerBar.qml" line="132"/>
|
<location filename="../src/qml/components/PlayerBar.qml" line="96" />
|
||||||
<source>Now playing: </source>
|
<source>Now playing: </source>
|
||||||
<translation>Сейчас играет: </translation>
|
<translation>Сейчас играет: </translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/PlayerBar.qml" line="133"/>
|
<location filename="../src/qml/components/PlayerBar.qml" line="97" />
|
||||||
<source>Now playing: No track info</source>
|
<source>Live radio</source>
|
||||||
<translation>Сейчас играет: нет информации о треке</translation>
|
<translation>Прямой эфир</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/PlayerBar.qml" line="193"/>
|
<source>Now playing: No track info</source>
|
||||||
|
<translation type="vanished">Сейчас играет: нет информации о треке</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/PlayerBar.qml" line="163" />
|
||||||
<source>Mute</source>
|
<source>Mute</source>
|
||||||
<translation>Без звука</translation>
|
<translation>Без звука</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/PlayerBar.qml" line="193"/>
|
<location filename="../src/qml/components/PlayerBar.qml" line="163" />
|
||||||
<source>Unmute</source>
|
<source>Unmute</source>
|
||||||
<translation>Со звуком</translation>
|
<translation>Со звуком</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -293,56 +405,82 @@
|
|||||||
<context>
|
<context>
|
||||||
<name>QuickFilters</name>
|
<name>QuickFilters</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="49"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="173" />
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="120"/>
|
|
||||||
<source>Genre:</source>
|
<source>Genre:</source>
|
||||||
<translation>Жанр:</translation>
|
<translation>Жанр:</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="56"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="65" />
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="131"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="184" />
|
||||||
<source>Rock</source>
|
<source>Rock</source>
|
||||||
<translation>Рок</translation>
|
<translation>Рок</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="62"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="73" />
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="137"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="190" />
|
||||||
<source>News</source>
|
<source>News</source>
|
||||||
<translation>Новости</translation>
|
<translation>Новости</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="68"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="81" />
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="143"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="196" />
|
||||||
<source>Jazz</source>
|
<source>Jazz</source>
|
||||||
<translation>Джаз</translation>
|
<translation>Джаз</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="76"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="104" />
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="155"/>
|
<source>GB</source>
|
||||||
|
<translation>GB</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/QuickFilters.qml" line="130" />
|
||||||
|
<source>Name</source>
|
||||||
|
<translation>Название</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/QuickFilters.qml" line="141" />
|
||||||
|
<source>Bitrate</source>
|
||||||
|
<translation>Битрейт</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/QuickFilters.qml" line="208" />
|
||||||
<source>Country:</source>
|
<source>Country:</source>
|
||||||
<translation>Страна:</translation>
|
<translation>Страна:</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="83"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="96" />
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="166"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="219" />
|
||||||
<source>US</source>
|
<source>US</source>
|
||||||
<translation>US</translation>
|
<translation>US</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="89"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="53" />
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="172"/>
|
<source>Quick access</source>
|
||||||
|
<translation>Быстрый доступ</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/QuickFilters.qml" line="118" />
|
||||||
|
<source>Sort</source>
|
||||||
|
<translation>Сортировка</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/QuickFilters.qml" line="152" />
|
||||||
|
<source>Votes</source>
|
||||||
|
<translation>Голоса</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/QuickFilters.qml" line="225" />
|
||||||
<source>UK</source>
|
<source>UK</source>
|
||||||
<translation>UK</translation>
|
<translation>UK</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="95"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="112" />
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="178"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="231" />
|
||||||
<source>FR</source>
|
<source>FR</source>
|
||||||
<translation>FR</translation>
|
<translation>FR</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="101"/>
|
<location filename="../src/qml/components/QuickFilters.qml" line="237" />
|
||||||
<location filename="../src/qml/components/QuickFilters.qml" line="184"/>
|
|
||||||
<source>WORLD</source>
|
<source>WORLD</source>
|
||||||
<translation>Мир</translation>
|
<translation>Мир</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -350,89 +488,260 @@
|
|||||||
<context>
|
<context>
|
||||||
<name>RadioBackend</name>
|
<name>RadioBackend</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/radiobackend.cpp" line="424"/>
|
|
||||||
<location filename="../src/radiobackend.cpp" line="868"/>
|
|
||||||
<source>Stream URL must use http:// or https://</source>
|
<source>Stream URL must use http:// or https://</source>
|
||||||
<translation>URL-адрес потока должен использовать http:// или https://</translation>
|
<translation type="vanished">URL-адрес потока должен использовать http:// или https://</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/radiobackend.cpp" line="433"/>
|
|
||||||
<location filename="../src/radiobackend.cpp" line="879"/>
|
|
||||||
<source>Manual</source>
|
<source>Manual</source>
|
||||||
<translation>Справка</translation>
|
<translation type="vanished">Справка</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/radiobackend.cpp" line="540"/>
|
|
||||||
<location filename="../src/radiobackend.cpp" line="564"/>
|
|
||||||
<location filename="../src/radiobackend.cpp" line="803"/>
|
|
||||||
<source>Last played</source>
|
<source>Last played</source>
|
||||||
<translation>Последнее</translation>
|
<translation type="vanished">Последнее</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>RadioBrowser</name>
|
<name>RadioBrowser</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/radiobrowser.cpp" line="157"/>
|
|
||||||
<source>Network error</source>
|
<source>Network error</source>
|
||||||
<translation>Ошибка сети</translation>
|
<translation type="vanished">Ошибка сети</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>SearchToolbar</name>
|
<name>SearchToolbar</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/SearchToolbar.qml" line="29"/>
|
<location filename="../src/qml/components/SearchToolbar.qml" line="30" />
|
||||||
<location filename="../src/qml/components/SearchToolbar.qml" line="89"/>
|
<source>Search stations (name, genre, country)...</source>
|
||||||
|
<translation>Поиск станций (название, жанр, страна)...</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SearchToolbar.qml" line="60" />
|
||||||
|
<location filename="../src/qml/components/SearchToolbar.qml" line="115" />
|
||||||
|
<source>Clear</source>
|
||||||
|
<translation>Очистить</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SearchToolbar.qml" line="81" />
|
||||||
<source>Search stations (name, genre, country)</source>
|
<source>Search stations (name, genre, country)</source>
|
||||||
<translation>Поиск станций (название, жанр, страна)</translation>
|
<translation>Поиск станций (название, жанр, страна)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/SearchToolbar.qml" line="44"/>
|
<location filename="../src/qml/components/SearchToolbar.qml" line="49" />
|
||||||
<location filename="../src/qml/components/SearchToolbar.qml" line="114"/>
|
<location filename="../src/qml/components/SearchToolbar.qml" line="104" />
|
||||||
<source>Search</source>
|
<source>Search</source>
|
||||||
<translation>Поиск</translation>
|
<translation>Поиск</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/SearchToolbar.qml" line="54"/>
|
|
||||||
<source>Sort A-Z</source>
|
<source>Sort A-Z</source>
|
||||||
<translation>Сорт. А-Я</translation>
|
<translation type="vanished">Сорт. А-Я</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/SearchToolbar.qml" line="63"/>
|
|
||||||
<source>Sort Bitrate</source>
|
<source>Sort Bitrate</source>
|
||||||
<translation>Сорт. битрейт</translation>
|
<translation type="vanished">Сорт. битрейт</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/SearchToolbar.qml" line="72"/>
|
|
||||||
<source>Sort Votes</source>
|
<source>Sort Votes</source>
|
||||||
<translation>Сорт. голоса</translation>
|
<translation type="vanished">Сорт. голоса</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/SearchToolbar.qml" line="160"/>
|
|
||||||
<source>Tip: you are not limited to DE/NL. Search worldwide by country, genre, or station name.</source>
|
<source>Tip: you are not limited to DE/NL. Search worldwide by country, genre, or station name.</source>
|
||||||
<translation>Совет: вы не ограничены DE/NL. Ищите по всему миру, по стране, жанру или названию станции.</translation>
|
<translation type="vanished">Совет: вы не ограничены DE/NL. Ищите по всему миру, по стране, жанру или названию станции.</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>SidebarNavigation</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SidebarNavigation.qml" line="52" />
|
||||||
|
<source>Stations</source>
|
||||||
|
<translation>Станции</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SidebarNavigation.qml" line="64" />
|
||||||
|
<source>Top Global</source>
|
||||||
|
<translation>Всемирный топ</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SidebarNavigation.qml" line="71" />
|
||||||
|
<source>Germany</source>
|
||||||
|
<translation>Германия</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SidebarNavigation.qml" line="78" />
|
||||||
|
<source>Netherlands</source>
|
||||||
|
<translation>Нидерланды</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SidebarNavigation.qml" line="93" />
|
||||||
|
<source>Library</source>
|
||||||
|
<translation>Библиотека</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SidebarNavigation.qml" line="106" />
|
||||||
|
<source>Favorites</source>
|
||||||
|
<translation>Избранные</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SidebarNavigation.qml" line="113" />
|
||||||
|
<source>History</source>
|
||||||
|
<translation>История</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SidebarNavigation.qml" line="85" />
|
||||||
|
<source>Worldwide</source>
|
||||||
|
<translation>Весь мир</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Search</source>
|
||||||
|
<translation type="obsolete">Поиск</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SidebarNavigation.qml" line="127" />
|
||||||
|
<source>Add Station</source>
|
||||||
|
<translation>Добавить станцию</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SidebarNavigation.qml" line="134" />
|
||||||
|
<source>About BearWave</source>
|
||||||
|
<translation>О BearWave</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/SidebarNavigation.qml" line="142" />
|
||||||
|
<source>Resume</source>
|
||||||
|
<translation>Продолжить</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>StationCard</name>
|
<name>StationCard</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/StationCard.qml" line="183"/>
|
<location filename="../src/qml/components/StationCard.qml" line="49" />
|
||||||
|
<source>votes</source>
|
||||||
|
<translation>голосов</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationCard.qml" line="51" />
|
||||||
<source>Stream</source>
|
<source>Stream</source>
|
||||||
<translation>Поток</translation>
|
<translation>Поток</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/StationCard.qml" line="196"/>
|
<location filename="../src/qml/components/StationCard.qml" line="218" />
|
||||||
<source>Edit Station</source>
|
<source>Edit Station</source>
|
||||||
<translation>Редактировать станцию</translation>
|
<translation>Редактировать станцию</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/StationCard.qml" line="210"/>
|
<location filename="../src/qml/components/StationCard.qml" line="235" />
|
||||||
<source>Removed from favorites</source>
|
<source>Removed from favorites</source>
|
||||||
<translation>Убрать из избранного</translation>
|
<translation>Удалено из избранного</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/qml/components/StationCard.qml" line="210"/>
|
<location filename="../src/qml/components/StationCard.qml" line="235" />
|
||||||
<source>Added to favorites</source>
|
<source>Added to favorites</source>
|
||||||
<translation>Добавить в избранное</translation>
|
<translation>Добавлено в избранное</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>StationDetailsPanel</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="45" />
|
||||||
|
<source>Stream URL copied!</source>
|
||||||
|
<translation>URL потока скопирован!</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="188" />
|
||||||
|
<source>Details</source>
|
||||||
|
<translation>Подробности</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="137" />
|
||||||
|
<source>Pause</source>
|
||||||
|
<translation>Пауза</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="137" />
|
||||||
|
<source>Play</source>
|
||||||
|
<translation>Слушать</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="154" />
|
||||||
|
<source>Favorite</source>
|
||||||
|
<translation>Избранное</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="154" />
|
||||||
|
<source>Add</source>
|
||||||
|
<translation>Добавить</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="161" />
|
||||||
|
<source>Removed from favorites</source>
|
||||||
|
<translation>Удалено из избранного</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="161" />
|
||||||
|
<source>Added to favorites</source>
|
||||||
|
<translation>Добавлено в избранное</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="166" />
|
||||||
|
<source>Groups</source>
|
||||||
|
<translation>Группы</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="170" />
|
||||||
|
<source>Groups coming soon</source>
|
||||||
|
<translation>Группы скоро появятся</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="251" />
|
||||||
|
<source>Tags</source>
|
||||||
|
<translation>Теги</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="298" />
|
||||||
|
<source>Links</source>
|
||||||
|
<translation>Ссылки</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="200" />
|
||||||
|
<source>Codec:</source>
|
||||||
|
<translation>Кодек:</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="205" />
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="217" />
|
||||||
|
<source>Unknown</source>
|
||||||
|
<translation>Неизвестно</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="212" />
|
||||||
|
<source>Bitrate:</source>
|
||||||
|
<translation>Битрейт:</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="224" />
|
||||||
|
<source>Votes:</source>
|
||||||
|
<translation>Голоса:</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="305" />
|
||||||
|
<source>Open homepage</source>
|
||||||
|
<translation>Открыть сайт</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="316" />
|
||||||
|
<source>Copy stream URL</source>
|
||||||
|
<translation>Копировать URL потока</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="344" />
|
||||||
|
<source>No station selected</source>
|
||||||
|
<translation>Нет выбранной станции</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../src/qml/components/StationDetailsPanel.qml" line="352" />
|
||||||
|
<source>Select a station from the list
|
||||||
|
to view details.</source>
|
||||||
|
<translation>Выберите станцию из списка,
|
||||||
|
чтобы просмотреть подробности.</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@@ -461,34 +770,28 @@
|
|||||||
<context>
|
<context>
|
||||||
<name>SystemTrayManager</name>
|
<name>SystemTrayManager</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/systemtraymanager.cpp" line="50"/>
|
|
||||||
<source>BearWave</source>
|
<source>BearWave</source>
|
||||||
<translation>BearWave</translation>
|
<translation type="vanished">BearWave</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/systemtraymanager.cpp" line="89"/>
|
|
||||||
<source>Pause</source>
|
<source>Pause</source>
|
||||||
<translation>Пауза</translation>
|
<translation type="vanished">Пауза</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/systemtraymanager.cpp" line="89"/>
|
|
||||||
<source>Play</source>
|
<source>Play</source>
|
||||||
<translation>Слушать</translation>
|
<translation type="vanished">Слушать</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/systemtraymanager.cpp" line="98"/>
|
|
||||||
<source>Hide</source>
|
<source>Hide</source>
|
||||||
<translation>Скрыть</translation>
|
<translation type="vanished">Скрыть</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/systemtraymanager.cpp" line="98"/>
|
|
||||||
<source>Show</source>
|
<source>Show</source>
|
||||||
<translation>Показать</translation>
|
<translation type="vanished">Показать</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/systemtraymanager.cpp" line="62"/>
|
|
||||||
<source>Quit</source>
|
<source>Quit</source>
|
||||||
<translation>Выйти</translation>
|
<translation type="vanished">Выйти</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
|
|||||||
Reference in New Issue
Block a user