From 4806f9306ac5b3b167180cd17894228d919b527d Mon Sep 17 00:00:00 2001 From: Sebastian Palencsar Date: Sat, 27 Jun 2026 13:21:55 +0200 Subject: [PATCH] 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. --- .gitignore | 9 + CHANGELOG.md | 9 + CONTRIBUTING.md | 10 + README.md | 24 +- screenshots/screen6.png | Bin 0 -> 106646 bytes src/main.cpp | 9 + src/qml.qrc | 8 +- src/qml/Main.qml | 228 +++- src/qml/components/AboutDialog.qml | 212 --- src/qml/components/AboutPage.qml | 347 +++++ src/qml/components/HeaderNavigation.qml | 125 +- src/qml/components/PlayerBar.qml | 251 ++-- src/qml/components/QuickFilters.qml | 89 +- src/qml/components/SearchToolbar.qml | 106 +- src/qml/components/SidebarButton.qml | 59 + src/qml/components/SidebarNavigation.qml | 153 +++ src/qml/components/StationCard.qml | 122 +- src/qml/components/StationDetailsPanel.qml | 359 +++++ src/qml/components/StationListPanel.qml | 4 +- src/qml/components/qmldir | 7 +- src/qml/theme/BearTheme.qml | 22 +- tests/qrc_resources_test.cpp | 3 +- translations/bearwave_de.ts | 1428 ++++++++++++-------- translations/bearwave_ru.ts | 1411 +++++++++++-------- 24 files changed, 3202 insertions(+), 1793 deletions(-) create mode 100644 screenshots/screen6.png delete mode 100644 src/qml/components/AboutDialog.qml create mode 100644 src/qml/components/AboutPage.qml create mode 100644 src/qml/components/SidebarButton.qml create mode 100644 src/qml/components/SidebarNavigation.qml create mode 100644 src/qml/components/StationDetailsPanel.qml diff --git a/.gitignore b/.gitignore index f5ed8fd..2a4c6ad 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,11 @@ build.md ## Build directories build/ +build-about/ build-ci/ +build-fix/ +build-maclayout/ +build-test/ build-arch-ci/ build-dir/ cmake-build-*/ @@ -36,6 +40,11 @@ test-*.json pkg/ src/bearwave/ bearwave/ +flatpak_repo/ +.flatpak-builder/ +.flatpak-builder-manifest.json +scratch/ +spalencsar-README.md *.pkg.tar.zst *.pkg.tar.zst.sig *.pkg.tar.xz diff --git a/CHANGELOG.md b/CHANGELOG.md index 098f752..e8e08c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,14 +10,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - 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 - Flatpak repository signing key replaced with `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 - 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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92fa3b7..59f23b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,6 +37,14 @@ If you changed 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: ```bash @@ -51,6 +59,7 @@ ctest --test-dir build --output-on-failure - Keep network operations asynchronous. - Preserve MPRIS behavior and user state compatibility. - 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 @@ -63,6 +72,7 @@ Please smoke-test the area you changed when possible: - favorites persist across restart - resume restores last station and volume - 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 diff --git a/README.md b/README.md index 63ef8a2..e19b2c3 100644 --- a/README.md +++ b/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](screenshots/screen01.png) | ![Station Browser](screenshots/screen02.png) | -| ![About Dialog](screenshots/screen03.png) | ![Additional View](screenshots/screen04.png) | +| ![About Page](screenshots/screen03.png) | ![Additional View](screenshots/screen04.png) | | ![World View](screenshots/screen05.png) | | 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 - system tray integration for background playback - 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 @@ -216,14 +217,14 @@ Add distributions here only after explicit testing, not by assumption alone. ### 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 - 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 -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 @@ -290,6 +291,16 @@ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release 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 From the build directory: @@ -425,13 +436,14 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for local build and review expectations. ## Development Notes - 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` - backend orchestration: `src/radiobackend.cpp` - stream playback: `src/bearplayer.cpp` - API layer: `src/radiobrowser.cpp` - MPRIS adapter: `src/mprisadaptor.cpp` - desktop notifications: `src/notificationmanager.cpp` +- embedded GPL license resource: `qrc:/assets/legal/gpl-3.0.txt` from `LICENSE` - unit tests: `tests/` See [CHANGELOG.md](CHANGELOG.md) for release history. For contributor and agent guardrails, see `AGENTS.md`. diff --git a/screenshots/screen6.png b/screenshots/screen6.png new file mode 100644 index 0000000000000000000000000000000000000000..4be28777b69ef452f959c1ce1f0e69877123ceec GIT binary patch literal 106646 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 z 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±4#~*8OAQuICEZk}u?Q1`kT_w=9=Mqg&s=HOE8U+mm+z3dN@i4wt(Qp(oFP z<)~)ozn&M{Z`mH~{qC<&+1*c9Z{6U6k8yYB1<$!d%)i&sevD52dLhhEkGbdeaP8z- zQrRQcf*vqblzQV`=h8!6k7r~$Ao*LTN&8>H&i5ZU*Lyy1y9WEQAJ6Fg8eN&2uaDI# zj`=p759K~LUAB!7Y@UQ4H>I%z(V2p; zU*F%8Y}bD6p6Pl{NIZVpu!44?t)lA9K?l^^j4N@56jnm^1C!U$)%)%V6vqcapSbJ= z`TfQV59~Q_V^QYu-S6-5)f*jq2drVb|A|PYs~wDI$G?FKe*PI3t2WxIg`=|j72RH) z7Quq94EC>j2>{q`I8Kxl&$E#RALNqcQ^nTg}=IBeDfh0h#TMc z0MeAvTl|iIjPys;YZxXi_<{>(RZDelt8!a#+T==Pc~{5osG3OXF$FJo$Ox_CG!@B2{Lj^b>4tN}4I z!A<=7ZGZiS*-qEFhhfG*{zqqmJ|cHw#IFbUqxbiB*AfG#-G0iAjGunK9ebnWS^pE` zrxs;6Q1TMLj}Av51;AtTE)rcNzLPw<$74 z$}Y;AYZLW@tHIQ{VP>1$G|I-!UU3uXDx|LRp>WFGm8a_7>Wp=(?_*LtnEpX zUH1AtsD5X^CLM17J#6HNK_3$SorKQC3*OT&&Q*LaGTILf!7C$&F$@rexSAOorM_&} zCP|l}saeJllap#g&YsMwXIX%b@m0li7DP@HJCE`EDv1P0EuuP?;s*D3U2fA6wh&W> z^ZvSK9BrMs-$X9l(%)*|apqCf{#jC+&I)M%Gy9Za43WG3&9gBw4 zSdGG)G$ctBBdtMi>*gvc2ys`c5+wF$=qSC#5)sKh1k>T4L&|b63SS`2L~b4*AGPk_DWd^xM`1vLw@2Vo3or&26O z$PXRT06H+jv9t#)d9368WL7TU1c@w;^H&t;i66cu6 z_0M!)lJsg;MuGK1<0I5PgOJtZBg*Hj-CBRSb1n}&w|W0X-}l+&7(v&Z*V@+wYSY)@ zfziFp;!~jiRX=G)8ODN;=MtFt2Yqf7OPDu^3&LI5pr)IYzRTzPZMgaWJ?nqb-v(@S#qJnOEtH4^bhws2 zvQnjg$Uq9|-|AK$=-OX)>7ls=q*5WM`1G*e+v*ZvYuta-2B$5vPc_Nd>z(r^zMUUf9 znCJ+ARxBMms#xU)aT4>}3ACE`bJejuJH?fdQu=Z#dT^tt5n1z??6ZG2j_;iOfj9h_!n%oAime z{a61#fj8{qlBir{AZmq3I?wWZf{it6LCh8jG#J~TI{cC{rw|&*ynzNMOp(dwtxPDq^`##P{v6K+58hLJ^f~ZBwcs{5>sF!G0*U+< z7%CZ69Y6>P0#rblAO;0SV?{neQjpJCBXvw1f=$K~u#^^Ry{5OlistYf$N#{viW9D< znoDUI#?b@x!ZkfQ9#oIv(xSjXRb6QOr5 z$%`wLk2avCWc78(8(PIGD*2UNQKu`V675>S+zw0Pj}%4bo**z1aJVn!L3z{}3IP(5 zp}&%nFA?l;6&*s-`QWWAqPc~)|#_`R@40wa~=4`k5a z-FQb&6hOy}<_{(1Z-bcrccFZP((qge_Kt4EQMflpo1K1B>hiz{3F+IyOw#^{o~{llgF-)NAx(9*8tLhEU&NVi|FYQ&x-(-d1d-p7D{(ej1?342UbvC-^x+1$LY~kY zeL!3Pzj|A$`aH;O^`+4rSyl*(s2CjPo zhhm@0TQ=+c?#Y&1eehwI@ux)LDPiSL$nFFIr&((iOY%230zI;Iz}}_2A|N7^oC}N^ zhYIJzq5bn6}=hx&&~edTczguJaL>_!6cJBc*>9- z#slt`Ptpl0Hg#_828n-xa#%@A;JB8HBG>kB2;`NGxQ(C0vDg?3DdDlN;8#&RaYuv7 ze)AjG5zuAWigD4{@uP$cOK`i_GQWK-0oUno}g?B}dwb+8@z1@umZtl*N)YK4x zYMfAEJ1B#a?MgHe&vT}a41Oao5SGu~6iTTtBBZp2w9t$(eLz~gbRJPfNg#EG7+GjRpB4%b_3LHtu74j77?zfn zQZoVtS&fU7aT@Qwh2ti3M`3sFkW_pmw2#Xa8VxTO1AsDV8~{Oc3EXNV$AUaT6yUPr z>6HP;peX}(G|?#wRSyo>PS74u796FOa52Y6kescOI?b?8B!ZXbt>UOK3&ukIjm@gK z>v>Y8)LFIM(cof~QGq3k$B|LEO|C1E3m7YK+G4_mX@->)*hyBQv6hn;sfmFjGkX+c zV)1N0{oPAbaV*2Y8%E#%zYCo;;<1WNt&lGGx&=dO;s3z38>3HJ9^Nnqj6-LnkoIr% zlN6gTMQ3V=z~B>Y3x~&|g2~tNyTcXt6ODx)ocH4VM5W?(WlRSRMVR012|Tm%^8r6F zk-@HC-~sULgz>x)({Y*-OMSv(+b7W$`so!i_4E@KZVNO{V+{k&+c{fmnGB~5zfzVb z^d7)5tb&+>3vlD>Ci>PupkXqqR@eVTCs)6+dC6 zXvtydJqNLHX4p(oK;bm(Y@cH30zf#v1bU93Zv;UcM?4%^8-bRmh%=A3MJZ*PAV>kG^`((A!@}eV1!4aM-sMkh2e5AqiuYMs&F-;FLhbd zhPK;bq@CoL`i(9vRgsvm@Wul3t<7jMRmhc-qPz z2l){wg%hkR4J$U(dAf7n(tkf~h8Q8VgCOGD7$N9A+01x2yv-MJkaFb{C};4IgiyBSAI$5FJcvnTpc&iDDobv z(_93WR8;EBEO8zL8^1xym98oih$3ZD7#QNvkz=tuLplMFR*o>Syaka=v?P6xOEw3X zMNH(iLsDU*Uo+H1V}%BoQ>D;6{E+~aVW@#yLV!pIRWAxJI})U%h54FZ$J{Yb*SxS| zxUye3bJd|{5*S*69Ly2eYPfnttX4X7p)b>Ku;oZ*X3?mygfbwBxO4xkZM>uW@YJsK z^~$pp>7IQ^8a{376^1>y%}3mepX;MOPBS2ZRx>gX0?{8gR^3c-Ms$g#wVT z24jB!RN&=x%F;Nm!Z}MgU$Olx#)_8P5`RdnDq9>vK3RU?WdzcE*1yhTYJ|wz+uQpX z9q%Rn_F-+$=*FAM|0JmY(X+I}&v=&GKL>IHpU;29(w_}Lb>&fju_U=ADg?#B2q9d9 zri5-p%fxpb5~> z!v?-f6u>NUtFjRVYLp@Hx?-rQ0dQIWawYM|C=AD+hEuz7#@rgOCdGV@ON~3u6Al+1 z?o&KCUqn@zJR>y?Pz*u!Oi<=RQ;ZVggIjAz&{`l`L7Df|V+(ot4rNM7g%I~is2>Wf z;&+FSi@d?(rrKH}u+&XcT3}|VC(!4*(N*yp@hcj< zfAMzuTsUA2+B$0Bb@{9^p1U7NMmf~{K2_`1#ebwJGy32E*iRGO=^#O$!{Ow^ar}Jc zHz_WsNQ8zVj3o)V0oQTm0umOJ-q8&B>Ve1rmq`x&e)bRbxE}6%w@ZNqR@L zZ)ay@0O7+?G{86fqr3n%8>oVb@Cw*M5PKoF?TR&tF* zyUr4ev?Y(QVypp%@ZxbLIN#_%{piw(L1cji4ydEc(A|U?z2{1y zd2puD%rNmq56n$u6eAphaT&xr}cF=)qT~aJpG?x#=f}J8I~B~3TQ-T zG}+0yYJw3a2?Wsu#UUJF0>T!y?!ocMT_j4oQR}4N6=^?%r8P}Fe#}S)hgdRE?jQxn zSu({I)Db;DB&-8S|Il%NMNAjfey*tKw#z(_9j3Z!*p8hdX2H2Ixn4?+(gdtcmYB(3 z7j1lFJU8@Z2c!d9#6dX_Z&7Du@Hpmmhf;WA+P#}Z5wwnWz!Du(3-e$x9wEjoO&}N^ zhzb%ShwK=j;5OERtF3^kcTQO(&1UgG<*?8bj--`@`G=kg&7e?(4o?F6#Dd%=U1+lK zor0qKe~rWK>`X@sW(qPb;HwyDo=`eALHi3m;Yewdr_N5%|eR1 z-P!BP*GAgI6|9p#fD{#uQCC?5%qb*A!2+5T+yT&Hq>Xfi#sC*kV!wY=xzS}Ym}zapjKSS$DSvs$E@jjZ-__H~qDw@XwWA)gwGKvQg{_c+yp6uCB}sQ< zL^BBpy!s52k^766<3xa{!OBAPeMPXueA)>9=p{K!se>aBPCiX1SL_A!Jil*X7*t50 z{&0s2hs>gz6SSdkM~aOC(se)t0rSwsF|h?TFrhew!a={Ukg3RH!#{~;3_F^2Y2v6! zzbiL{q5=}adwE<2N&G1})&rv{T=DTzN73c6qaHzrt@OMpE9G7vk5oPqID zaz*Rw5gX{M|EveGA1cNq=`=LDY%JN{C$g6~pcMfDjZ9$@M?GYQmHX8gE&od{MOfT) zK3Z9LG07yI8_u&z2w-b*e&nxOOkH{~0q`M7pLh;a4M%SEb_8dkYPMd{L>500Gl#U` z+T$BQS=jEgJk3olMvL&o@MI&SiTcslnX2+w#i{)Fs&HC%o!1{l^A&*V%?CoE?f=rIe4v4IEeV57Y46-q&IC3-T z&h%XQ^3zI&ME!!#xSWhsTZdn-xNx8?+!>jM6!fo_QUnloLxd>-y&D7YhXp{Br#`3> zK1yx_Lbp(Bdr}Dw2&Ce+DhA81Zf&c{0m4H5#(Y2d(5OF)*I$?f+TH2clr1`vHO;S# ze3I50Zg|~qWDXUzi@#qfj)a`G_s@!23XTm$*cG%R90{9+WvfSLI` za>k27Wzdbg-ZU0Q#uA;y;tn*ee-Hg=D}UnhV4W_l_gY2K=ZrW-E0|qH9YPkA8H$09 z+3D#4p#G5|cuf7z@(K8|Mm%Y2jtV`AkAJ`JkxujNl?9Uxt|;xEHsm}E1epF4v(LsV zDJv0>i9tXRTS$ottIir&Pg_MBo!!8U`0)|jD?GTnGEZCW=m*~fBuH0dNYD#TW5Tu; zizofq`XuoSB*<3NKnMz_0Ku=lul?xuU9qwz-0-T&ioX5VtK^}u5B1p*1|dUeR4@!L zW<(E?E{FkKT#5;kv-f@-5r*?iz#}B5r8d^`Lv<)3S;RwwB$BFp#&x2SY7ix1wv^s* zbg82RW=c0P=Hrosu{n~LES8DhKx!$#p)?fcN>Io?z^j$HD4YQ&Pq?7VFo8`&Y~m*) z>&#|wf$Mtzz_2{&klt?8i#z+Kcn%Ttp1n|#m{}$GV9{jGPUWM- zlJ;s8V#DF4GnWUqdgm8;N`=(%s~fQSM7Wx+%QD5cP38aoz~3B~FG}&A^#h8s%_=Em zRG!QBUhxZOBOE`0>s`hC%kV!NQVkA)ePoTjT%q|myI&JR6=zKrJWyN!yStSE*wpaK zmXJiaLeyfYkhsE0djK+27LuTzG>ioCKREPk($EbF7U_zQSQY`w@GHZb*(OflNOVd) zhc71X>rooG`nNHU35ImSIC12CRzXaL*UW(^7KAF8Ss*j=up$cHQ0ndI?cp3!A(*sY zmip@SeaVPunx^q>}jiIK+D<4xE`}QMMOE?;r8vijnFF_nIPq z9nHU8it-2t3ZV;G2*(zGD{uddm&WC?1fu@xyHY^4yTvxEEN(nNm}mQPEOdoRlWS8wS{ccYzyXEkkIsE zA~6AXoC8an|Lj&VthC1EKu-gZ{ zABzQ{`$E`li%7M8OLdp84653N^~@&snu&99fz=_YBP4)NSb_vC92J4fzZfiNA0>|M z9GyKyW~~9qWI2lg3kQb^n*J*omfQRD+s4&(5;>-P3Ma||0oZO0qQ@VGiY(Fe#3X&R z&Zm!8f8+R$XLur&*O3+CbNRP(b%kJ(LYBd|kDrsC)%)+>7asdnjNTyH`_r}2YhK@Q ztOfb`bj}JLdzz*mA>(1*5x+OlYwSp_C&6tGBytd>iUUK2<-@yYBr5; zES1ab`4alN!caLsy+xU!QMUTEiDU%-fC;*!t}NA*3MCizpM)VK0DTRd8ub&f zYOwegIV6s7LM;A0EOk?&&5`)$0^THULmb<;2`PhTg7(c9ql&2-q5=efd5knF2x1#m zz`rI-QvFH*Dp!$`^r&%sd1m-|QD3|)$xG6bTB?8W6u`~8gTu)){*6NfdNiRWr1FK% zk^IOs0MVSu+KF-bCs8?nf9ZjKJzT6O&%!UOJaKZEUT?@~yev>>4F3Lte{``bmLwuIS}X>xb_QIp=3* zyPqrBEx5mjn`}G>Vdje&x#PEUe^zL>W;pBLSrM(*M9bm-b$spk_PNpBWe!Z7q7pd-+7KQg+aJ7BSyWO5~uHFJGiVY7nqIP`0 z&Jj`Ri$9JID#2HcaIWaFvL!KGjaL8i(*j9~s=6`ap4<{AW#-M0QIC@`|iYNP2x&2aeU*@ zmZt3)-)EWmR`DZC7C3E*ga912Uu!$6!749|nEW>uiMeSWSD%~~H=NNWDm(hD|70e# z-f6QAZ9=ygL+(7xSD z&DQ*y&*?PLOQsW|iy6JU zABai8GcK|GDOdJQzOls%1SoHL^kIw1%VUcMt%tI5Du%%GSVYnQYAKLpQb2REEW20{4Wa3wv7LVoiV3xFbJ}%AO;8M?XWs&ncY!8n; zI_oaupDa6(wxg7ODUgMZrc%s_EJgW7!q|uAPp}?#zx@8LUBg0r%8Op`6~%6Y1r%Bg zRu0$sbt)(OP|Q4Z8LaaGua#s^=q5I1ZvL3N`YZhD?PbaPl8K?u&^Gg_(_2eNZ>O;O zSxMmIXbR0Fr>eXX1senax@ADmgKVavVdiOsfAz%ak{%}IpfhunVDyTz*g{0u8_mks zh$}sfsB(xoq&3orT_hc_N*OxA5RL`$zB3Nn{p3$dgPgd^ej2vG9DM~wxBaB+*o}&u z%-DN=Sev|t>+YGo##I(HdBlEDPD(oHe#YcFus8|EfMgbpi#}`s7a_Y$fSJm=x;KD)HTCX!kvlyKs)R4+U04V51tJ7u1;niO-ds%LEI?)oAV zZBNakyFY)eK;~>Ux@cT;eE*eX@i=6fI8`C=8GPYXwiIRTAV_}SF ze%NzaW{jZRNosdvtLGjg;TyznfQxcw@OZxDPfmOydN=# zy~Y+MBKla5WrM?F#`89^0s-vNRX>UHw{fST5WIi4@hR9}d=WR7k4+1=_-*R&XSxW_ zD&ofB)?w&7v0%>!UZyc~xfBfB+Z2!gh6O;?yke2Df-lp=H}UpQsg?PG z9@>1*mz+=I#ezd8o0n!?zc$x>jwYuZ*YAd#k=u=FDqGDq1&H`O<6eloQm_fTzs#}m zxorL%pSbw(;pZ>W_1?1>!cRu&Di{01<&ScB96WG9jkYL4F#2=QY`8P7g&g<#@CcDr zExs~>7kC;)=&F+6NvayQrls4wR}_%e|NZY(ZlRdK zf<6hYB9Cz;MPVSE{PPzYQGrxjrp+|&F9ef0|o2;e4XbtJXtx0{L_Nklmi`IEUKC&G;O&+HwL#Y2&AN`G8mMCL&G?5#o7e$9Egd<(+Y2wB zm%2KXV>^zFN6d(Dw~4gz-PozU7JKz3!^%X~8lNwCX69h}X_lce%%TsP(g38!E z^hmEi%wF}@(y`4<{6`6(*UN^=s2I~05hDbn3tuVGyknoM(X_?dIa6?1*L1m&oS|rM z0H@c2AkEIqGwhhvlInEAJ3-%sbxb>@SVvzW1l|0rw0)`??vMsfrpM|bl#c!t)qRIG zxYp%8^}9y{q4xvfmR~&B#`e7m`Tjt&wXZyUYU*NpeCT*_|H7(yLZYhqLJ^?|EC{zi zv*fY$MY)sJ6_w%O4GjdS^*%k4pHatj{pqQb7oOL%&n=T~ZBcJ>Vgu4;$Exy%k9hF4 zvcB2092VeK@-_}_P!YLp2@r`d_IGAnm2tiZAozLn{pigmWSif7shBR};*F$;iJ;>x zYMel8>)rjsJ-Yiytzy#?#ai&eTbBt_EuDIFnmLC}v{LDTA@0No?lsPT4Hz{wH4A^Q#n%#e!mL5i*ztr%$l+nH1_k?O>DJO?r&KdSh)y#mPn-r z;1x`#xDh>y8a5Xwd3X6{dACZq7+KhI1+|tIEv}d;m^6ZE<`Xj=2%pnMU#I^m5c{p z&Wymjuc;IqA6q7CmvhZIiDD9i``!Y5%2p}96LM_eEYkIQ$?*CTm#m*W<~HBp#jLdB z7Uu~=R&H@JbCthR*I8Jokv@jjhH*4l6q4w!ksY##;M1~27l=1(_}@LYjQ`91O1(fO z2dCVW9OfHYQ=4rUj>Nz9tNj+-1EFz^zyyfENx-ZkjYwEWyjKyIgtlR_hFX`+w6^zr`Zx*o zGb9LXc@D(UB1B0|Pb*4`%qDr$QNrN*gIi`!7d{%Y^1S?&q+b;ceJU?WQk1gU%1i zbmNR6*}r%f*3|4Oo+0`6o`j7uhu2hGw-@&24qOMf0%Me9b+R*#$a`$Plp};n-_gIs zBhIK2)O7nzVhCWEZoe>lA*`2jw>+4YJ(4^PjVZYfbwtRvY8k6+)x%a$T8RPR^I7Wn z;#wo!C}M*cwA6N1GCI!V=zDXjTGshPII;fjI`v<4z6ZTNf`x2Y>bBtjpxOv0F{s_h z`{7{bSf;`5L5agiB27j%cdh+LDyTabwUi!7A?ZtW#e%GIN(ne7iqiQBLpUNz3Ag6Y zDRjGQ&n&jm%Ii9z#TLWYya{#xkn>pC3MpKNZeelz?HOVl5ef+du3}O1Qy%EH$c0H}GSw5n(B*`u<;iD73{5ygl_C6nng(oxkp8`wf8wuHir~2`f zA?JO*csV(o*oY+1Xo8N`9>AQdME%3U_;(oP5MCkAd_VLYUSn+kQ+S>Fnkkk=yhL}) zKK)nDxKvRU9js_Zr~6#~8p|+`1SfI~AdZbJbb5_YDdvzy0-L6@=Znc_xTF_~R(Usw z2hbp?WZy>kY4<6wEL@-%LnIuR?O{u#K66y3OF89WB4hlMXbDuj z?wk^gwBI$n4Jq#7zi6|R+{2wap2QbC={qHk7~6A-HkE|Vz!eLo71UWR)p#cH+&oGO zv01;;R;b86+{k-2+ki=)82ko5CM2mSB~Fy9^v_;54~^Vu@;xP`d>t@76411yfF??~ z@ON0aGL2Wmt&5BiUeUiLO<f&_~Y@Dncn;D`yqXl zQOD2-ZQ8=z_#k4!;67|XFP?}h{GL-7#|E8vPJ+(GllB-jJ9EYlZ4<(s|5iiT(k zk6d_d)UVe`KHnK{!YsAz^pL)8?4ow`5c-Z~lZl`LOywm}9WB{+9e@1-mHU#mpT~|u zrHJL2N;GLjgB^#|gQc!jo`0vDuK%8I1=+VI7Jfa@d~8e~?QR(O(>i?AdCo<(@f6Z> zlXcMbkX`@S^q?eQk8&3xII%4FL|0j7TGj8DVDwyVWGo<|q)RIftLhpx;Vg?bZ6f3H zC10l@TjIxews(v$GNv~YBZts7fsoW^NFI0c?rfWLz8QO=W2R_uPGi3r?CN^YeUD`X zQW}@^ylo#Rdr-Pqw@rXq-l3cGPKlVO#mR3qTxM?7vQ)t?{|BVutgi=C-f3l&_DHmz z2n%t8NzrAaDp3gWm5dMZI{^~-a)=sc+9k}eADoi`zewBZVceT1%$xP>ZDkS5;NMbl zhw{-jnx~$<3mb>DC5l&nC`MVDh>k|Z{l}0Gu$m1+S|vi}_XaxdrChNO+K@*Mr$9k=u6@-K0#-*`TwWP)v z;Gn9cvZBqPE#ai%BFL3rI=plxeI7XVqX-R`t#lRqgMYK`Pk)oHH+EOH<3k6xf`+S0VN+X}Rwd8tS+CRo$J* z`_j1yd8_IBx3jI4dYf}*?8XY!3aehqUTzu)&Clux=Rw>1i&Ynv|D$pc()cS0nnV=_ zKH#h~%hHR28!0CGPq_iE##d{9YZoT9Xy)1W?c|tD_cCc#OZCRlh{%2^be?>Bei^9* z+S3a(1Y(rPh)EA zgF2)&TEQ#E0E#C_TQqmJKT7G<@@SfWqAoKI-!_{9QJavx#c@q)CTGs%>NzycXZfBM z#@Cbl+gJDNSF4)sm85eEQX>8vI~l@3S@AMBv<(a=HQnVv4Otq?HqbENi$tos#OKQN zimT*?CTL9-lu4S1oT@)k8pVl}iWZ-ul9HIFztpPpyHO6e;k_EfJkYVPr%~XjGr{mo?3G)#z@`5ATa=rko770*dXd+&d?boP-T~H1Ea`VcgL8G+yRO(w_ z*-QO&iSttFj3YuBDOK0IhwcznZl=iO`Nx^ENPe)VatuWGyWWeeu$T2!{_HEF%k_+}e)ZaV zJE)-ic1=QgY(B{E0!ihUu1$ROG(9-3%t5xM*YKt=+m*+kbx(;)uqwv8zjnz<{QQS) zrv61)-%pIoWwq3{6!+YcN@{XTUhB3n(@EjI=#z}B`wj*#ek!iha-90YCPC(glkn>n zQ?}?WCBKcIM2hF~m_^3uTr_K6w=gog$?uqoModZ1s^c2Z^N()jH#VwwohXxa_rbgS z=l5;bDfsk#zZ)=dCA052ew4FR=JHGRW6!w1Se;LN_<~zv?(S~0&=nDnS7cIS`E>k@;g&0M4e0j4jDlW4u zn(JNO?0#^R3xr!G8#UE`V$I&~dRE@1E;2je^Ywc!#y6Kp#oV~13P~5h;jq%Km9{BQ zO|;eD-@Ns;eAk<$=hF80U3;0ID+UfcAhFIXa=E{8-2GdBTJ)?JtYg!Dk}CiD)Y@2> z{E^V3yC&^<6U-a7t;}}eX}%S=vm8u!INZFyeXBRP5C;-FR!#X^8`8|ZUra z8l)0-e6pzA?tgFgZ6wEte!3@9x&3^>(~oOo{ZDOUdX={6V8PSEs^a#OyN#oF1BdUx z26kvi-J846ynM#PQ;$t1_pZGQRD1rJq>eqCM%Cx*O_#15zPi>K=EDwczk9P60~KF< z_0oIe+iyA63)m!d<}(Nde{Mh7m8mBE{Nvk{*9M^424wi7$n;pxoA{W$mg)x zxrP7oHf{3H|7URYirlM{Y-cN)FZggQRQy%;x&Wpp>3HUA1GU2`s*%g #include #include +#include #include #include #include @@ -56,9 +57,17 @@ int main(int argc, char *argv[]) engine.addImportPath(QStringLiteral("qrc:/qml")); 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("bearwaveVersion", QStringLiteral(BEARWAVE_VERSION)); engine.rootContext()->setContextProperty("bearwaveBuildId", QStringLiteral(BEARWAVE_GIT_HASH)); + engine.rootContext()->setContextProperty("bearwaveLicenseText", licenseText); auto *mprisRoot = new MprisRootAdaptor(&backend, &app); auto *mprisPlayer = new MprisPlayerAdaptor(&backend); diff --git a/src/qml.qrc b/src/qml.qrc index f9a0eb5..95fcfdb 100644 --- a/src/qml.qrc +++ b/src/qml.qrc @@ -11,12 +11,15 @@ qml/components/WorldCategoryHeader.qml qml/components/WorldCategories.qml qml/components/PlayerBar.qml - qml/components/AboutDialog.qml + qml/components/AboutPage.qml qml/components/AddStationDialog.qml qml/components/EditStationDialog.qml qml/components/LoadingOverlay.qml qml/components/ToastPopup.qml qml/components/StationListPanel.qml + qml/components/SidebarNavigation.qml + qml/components/SidebarButton.qml + qml/components/StationDetailsPanel.qml qml/utils/FlagUtils.js @@ -25,5 +28,6 @@ ../assets/ui/globe.svg ../assets/ui/github.svg ../assets/ui/linkedin.svg + ../LICENSE - \ No newline at end of file + diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 0b1b127..531edef 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -11,9 +11,9 @@ import components 1.0 ApplicationWindow { id: root title: qsTr("BearWave") - width: 980 - height: 700 - minimumWidth: 520 + width: 1400 + height: 720 + minimumWidth: 900 minimumHeight: 460 visible: true @@ -26,7 +26,9 @@ ApplicationWindow { property var backend: (typeof radioBackend !== "undefined" ? radioBackend : null) readonly property string appVersion: (typeof bearwaveVersion !== "undefined" ? ("" + bearwaveVersion) : Qt.application.version) 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 string activeQuickFilter: "" property string selectedWorldCategory: "" @@ -36,12 +38,54 @@ ApplicationWindow { property alias stationList: stationPanel.stationList property alias addDialog: addDialogPane property alias editDialog: editDialogPane - property alias aboutDialog: aboutDialogPane function toast(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() { searchTimer.stop() if (searchField.text !== "") { @@ -85,6 +129,9 @@ ApplicationWindow { onCurrentPageChanged: { contentOpacity = 0.85 contentFadeRestart.restart() + if (currentPage !== "about") { + selectedStation = null + } if (currentPage !== "search") { resetSearchFilter() } @@ -111,34 +158,61 @@ ApplicationWindow { Rectangle { anchors.fill: parent - gradient: Gradient { - GradientStop { position: 0.0; color: BearTheme.bgA } - GradientStop { position: 1.0; color: BearTheme.bgB } - } + color: BearTheme.bgA } - ColumnLayout { - anchors.fill: parent - anchors.margins: 12 - spacing: 10 + SidebarNavigation { + id: sidebarNav + visible: !root.compactMode + 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 { - Layout.fillWidth: true - Layout.preferredHeight: headerContent.implicitHeight + 20 - radius: 12 + id: headerPanel + height: currentPage === "about" && !root.compactMode ? 1 : headerContent.implicitHeight + 18 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + radius: 0 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 { id: headerContent anchors.fill: parent - anchors.margins: 10 + anchors.margins: 9 spacing: 8 + visible: !(currentPage === "about" && !root.compactMode) HeaderNavigation { Layout.fillWidth: true app: root compactMode: root.compactMode + visible: root.compactMode } Rectangle { @@ -146,6 +220,7 @@ ApplicationWindow { Layout.preferredHeight: 1 color: BearTheme.cardBorder opacity: 0.6 + visible: root.compactMode } SearchToolbar { @@ -154,12 +229,14 @@ ApplicationWindow { app: root compactMode: root.compactMode searchTimer: searchTimer + visible: currentPage !== "about" } QuickFilters { Layout.fillWidth: true app: root compactMode: root.compactMode + visible: currentPage !== "about" } Label { @@ -173,12 +250,23 @@ ApplicationWindow { } } - Rectangle { - Layout.fillWidth: true - Layout.fillHeight: true - radius: 12 - color: BearTheme.panel - border.color: BearTheme.cardBorder + PlayerBar { + id: playerPanel + height: implicitHeight + anchors.left: parent.left + anchors.right: parent.right + 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 opacity: contentOpacity @@ -186,37 +274,74 @@ ApplicationWindow { NumberAnimation { duration: 180; easing.type: Easing.OutCubic } } - ColumnLayout { - anchors.fill: parent - anchors.margins: 10 - spacing: 10 + 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 - WorldCategoryHeader { - Layout.fillWidth: true - visible: currentPage === "world" && selectedWorldCategory !== "" - app: root + Rectangle { + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + width: 1 + color: BearTheme.cardBorder + visible: !root.compactMode } - StationListPanel { - id: stationPanel - Layout.fillWidth: true - Layout.fillHeight: true - app: root - } + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 0 - WorldCategories { - Layout.fillWidth: true - Layout.fillHeight: true - visible: currentPage === "world" && selectedWorldCategory === "" - app: root - compactMode: root.compactMode + WorldCategoryHeader { + Layout.fillWidth: true + visible: currentPage === "world" && selectedWorldCategory !== "" + app: root + } + + StationListPanel { + id: stationPanel + Layout.fillWidth: true + Layout.fillHeight: true + app: root + } + + WorldCategories { + Layout.fillWidth: true + Layout.fillHeight: true + visible: currentPage === "world" && selectedWorldCategory === "" + app: root + compactMode: root.compactMode + } } } - } - PlayerBar { - Layout.fillWidth: true - app: root + StationDetailsPanel { + id: stationDetails + visible: !root.compactMode && currentPage !== "about" + width: 350 + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + app: root + } + + AboutPage { + visible: currentPage === "about" + anchors.fill: parent + compactMode: root.compactMode + appVersion: root.appVersion + buildId: root.appBuildId + licenseText: root.appLicenseText + } } } @@ -242,13 +367,6 @@ ApplicationWindow { compactMode: root.compactMode } - AboutDialog { - id: aboutDialogPane - compactMode: root.compactMode - appVersion: root.appVersion - buildId: root.appBuildId - } - ToastPopup { id: toastPopup window: root diff --git a/src/qml/components/AboutDialog.qml b/src/qml/components/AboutDialog.qml deleted file mode 100644 index 94f3363..0000000 --- a/src/qml/components/AboutDialog.qml +++ /dev/null @@ -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 ." - } - } - } - } - } -} \ No newline at end of file diff --git a/src/qml/components/AboutPage.qml b/src/qml/components/AboutPage.qml new file mode 100644 index 0000000..1fc214c --- /dev/null +++ b/src/qml/components/AboutPage.qml @@ -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" + } + } + } + } + } + } + } +} diff --git a/src/qml/components/HeaderNavigation.qml b/src/qml/components/HeaderNavigation.qml index 6d2dceb..750b64e 100644 --- a/src/qml/components/HeaderNavigation.qml +++ b/src/qml/components/HeaderNavigation.qml @@ -13,121 +13,13 @@ Item { required property var app required property bool compactMode - implicitWidth: compactMode ? compactNav.implicitWidth : desktopNav.implicitWidth - implicitHeight: compactMode ? compactNav.implicitHeight : desktopNav.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() - } - } - } - } + implicitWidth: compactNav.implicitWidth + implicitHeight: compactNav.implicitHeight ColumnLayout { id: compactNav anchors.left: parent.left anchors.right: parent.right - visible: root.compactMode spacing: 8 RowLayout { @@ -152,7 +44,8 @@ Item { Button { text: qsTr("About") - onClicked: app.aboutDialog.open() + highlighted: app.currentPage === "about" + onClicked: app.navigateToAbout() } Button { @@ -174,31 +67,31 @@ Item { Button { text: qsTr("Top") highlighted: app.currentPage === "top" - onClicked: loadTop() + onClicked: app.navigateToTop() } Button { text: qsTr("DE") highlighted: app.currentPage === "german" - onClicked: loadGerman() + onClicked: app.navigateToGerman() } Button { text: qsTr("NL") highlighted: app.currentPage === "dutch" - onClicked: loadDutch() + onClicked: app.navigateToDutch() } Button { text: qsTr("Favorites") highlighted: app.currentPage === "favorites" - onClicked: showFavorites() + onClicked: app.navigateToFavorites() } Button { text: qsTr("History") highlighted: app.currentPage === "history" - onClicked: showHistory() + onClicked: app.navigateToHistory() } } } diff --git a/src/qml/components/PlayerBar.qml b/src/qml/components/PlayerBar.qml index 6137ddc..120eb44 100644 --- a/src/qml/components/PlayerBar.qml +++ b/src/qml/components/PlayerBar.qml @@ -12,23 +12,34 @@ Rectangle { required property var app - implicitHeight: 108 - radius: 12 + implicitHeight: 76 + radius: 0 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 { anchors.fill: parent - anchors.margins: 10 + anchors.leftMargin: 12 + anchors.rightMargin: 12 + anchors.topMargin: 9 + anchors.bottomMargin: 9 spacing: 12 Rectangle { - Layout.preferredWidth: 88 - Layout.preferredHeight: 88 - radius: 8 - color: "#182637" + Layout.preferredWidth: 56 + Layout.preferredHeight: 56 + radius: 6 + color: "#34353b" clip: true - border.color: BearTheme.cardBorder + border.color: "transparent" Image { id: coverImage @@ -58,164 +69,122 @@ Rectangle { && app.backend.currentStation.favicon.startsWith("https://")) { return 8 } - return 16 + return 10 } } } ColumnLayout { Layout.fillWidth: true - Layout.fillHeight: true - spacing: 6 + Layout.alignment: Qt.AlignVCenter + spacing: 3 - RowLayout { + Label { Layout.fillWidth: true - spacing: 8 - - Label { - Layout.fillWidth: true - text: app.backend && app.backend.player - ? (app.backend.player.currentStationName || qsTr("No station selected")) - : qsTr("No station selected") - color: BearTheme.textMain - font.pixelSize: 16 - font.bold: true - 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 - } - } + text: app.backend && app.backend.player + ? (app.backend.player.currentStationName || qsTr("No station selected")) + : qsTr("No station selected") + color: BearTheme.textMain + font.pixelSize: 14 + font.bold: true + elide: Text.ElideRight } Label { Layout.fillWidth: true text: app.backend && app.backend.player && app.backend.player.currentNowPlaying.length > 0 ? (qsTr("Now playing: ") + app.backend.player.currentNowPlaying) - : qsTr("Now playing: No track info") + : qsTr("Live radio") color: BearTheme.textMuted font.pixelSize: 12 elide: Text.ElideRight } + } - RowLayout { - Layout.fillWidth: true - spacing: 8 + RowLayout { + Layout.alignment: Qt.AlignVCenter + spacing: 8 - Button { - text: "⏮" - Layout.preferredWidth: 44 - enabled: app.backend ? app.backend.hasPreviousStation() : false - onClicked: { - if (app.backend) { - app.backend.playPreviousStation() + Button { + text: "⏮" + Layout.preferredWidth: 42 + Layout.preferredHeight: 30 + enabled: app.backend ? app.backend.hasPreviousStation() : false + onClicked: { + if (app.backend) { + app.backend.playPreviousStation() + } + } + } + + Button { + text: app.backend && app.backend.player && app.backend.player.playing ? "⏸" : "▶" + Layout.preferredWidth: 42 + Layout.preferredHeight: 30 + onClicked: { + if (app.backend && app.backend.player) { + app.backend.player.togglePlayPause() + } + } + } + + Button { + text: "⏹" + Layout.preferredWidth: 42 + Layout.preferredHeight: 30 + onClicked: { + if (app.backend && app.backend.player) { + app.backend.player.stop() + } + } + } + + Button { + text: "⏭" + Layout.preferredWidth: 42 + Layout.preferredHeight: 30 + enabled: app.backend ? app.backend.hasNextStation() : false + onClicked: { + if (app.backend) { + app.backend.playNextStation() + } + } + } + + Button { + id: muteButton + text: (app.backend && app.backend.player && app.backend.player.volume > 0) ? "🔊" : "🔇" + Layout.preferredWidth: 36 + Layout.preferredHeight: 30 + property real lastVolume: 0.5 + + ToolTip.visible: hovered + ToolTip.text: (app.backend && app.backend.player && app.backend.player.volume > 0) + ? qsTr("Mute") : qsTr("Unmute") + + onClicked: { + if (app.backend && app.backend.player) { + if (app.backend.player.volume > 0) { + lastVolume = app.backend.player.volume + app.backend.player.setVolume(0) + } else { + app.backend.player.setVolume(lastVolume > 0 ? lastVolume : 0.5) } } } + } - Button { - text: app.backend && app.backend.player && app.backend.player.playing ? "⏸" : "▶" - Layout.preferredWidth: 44 - onClicked: { - if (app.backend && app.backend.player) { - app.backend.player.togglePlayPause() - } - } - } - - Button { - text: "⏹" - Layout.preferredWidth: 44 - onClicked: { - if (app.backend && app.backend.player) { - app.backend.player.stop() - } - } - } - - Button { - text: "⏭" - Layout.preferredWidth: 44 - enabled: app.backend ? app.backend.hasNextStation() : false - onClicked: { - if (app.backend) { - app.backend.playNextStation() - } - } - } - - Button { - id: muteButton - text: (app.backend && app.backend.player && app.backend.player.volume > 0) ? "🔊" : "🔇" - Layout.preferredWidth: 44 - property real lastVolume: 0.5 - - ToolTip.visible: hovered - ToolTip.text: (app.backend && app.backend.player && app.backend.player.volume > 0) - ? qsTr("Mute") : qsTr("Unmute") - - onClicked: { - if (app.backend && app.backend.player) { - if (app.backend.player.volume > 0) { - lastVolume = app.backend.player.volume - app.backend.player.setVolume(0) - } else { - app.backend.player.setVolume(lastVolume > 0 ? lastVolume : 0.5) - } - } - } - } - - Slider { - Layout.fillWidth: true - from: 0 - to: 1 - value: app.backend && app.backend.player ? app.backend.player.volume : 0.5 - onMoved: { - if (app.backend && app.backend.player) { - app.backend.player.setVolume(value) - } + Slider { + Layout.preferredWidth: app.width < 1180 ? 115 : 160 + from: 0 + to: 1 + value: app.backend && app.backend.player ? app.backend.player.volume : 0.5 + onMoved: { + if (app.backend && app.backend.player) { + app.backend.player.setVolume(value) } } } } } -} \ No newline at end of file +} diff --git a/src/qml/components/QuickFilters.qml b/src/qml/components/QuickFilters.qml index 8e58392..5d797c7 100644 --- a/src/qml/components/QuickFilters.qml +++ b/src/qml/components/QuickFilters.qml @@ -22,6 +22,7 @@ ColumnLayout { app.backend.loadByTag(tag) } + // Load country by country code function loadCountry(code, page) { if (!app.backend) return app.currentPage = page @@ -40,72 +41,124 @@ ColumnLayout { } } - RowLayout { + Flow { visible: !root.compactMode Layout.fillWidth: true - spacing: 6 + Layout.preferredHeight: childrenRect.height + width: parent.width + spacing: 8 + // Left: Schnellzugriff filters Label { - text: qsTr("Genre:") + text: qsTr("Quick access") color: BearTheme.textMuted + font.bold: true font.pixelSize: 11 - rightPadding: 10 + width: 86 + height: 30 + verticalAlignment: Text.AlignVCenter } Button { + width: 72 + height: 30 text: qsTr("Rock") highlighted: app.activeQuickFilter === "tag:rock" onClicked: loadTag("rock", "genre-rock") } Button { + width: 72 + height: 30 text: qsTr("News") highlighted: app.activeQuickFilter === "tag:news" onClicked: loadTag("news", "genre-news") } Button { + width: 72 + height: 30 text: qsTr("Jazz") highlighted: app.activeQuickFilter === "tag:jazz" onClicked: loadTag("jazz", "genre-jazz") } - Item { Layout.preferredWidth: 20 } - - Label { - text: qsTr("Country:") - color: BearTheme.textMuted - font.pixelSize: 11 - rightPadding: 10 + Rectangle { + width: 1 + height: 18 + color: BearTheme.cardBorder + opacity: 0.6 } Button { + width: 62 + height: 30 text: qsTr("US") highlighted: app.activeQuickFilter === "cc:US" onClicked: loadCountry("US", "country-us") } Button { - text: qsTr("UK") + width: 62 + height: 30 + text: qsTr("GB") highlighted: app.activeQuickFilter === "cc:GB" onClicked: loadCountry("GB", "country-gb") } Button { + width: 62 + height: 30 text: qsTr("FR") highlighted: app.activeQuickFilter === "cc:FR" onClicked: loadCountry("FR", "country-fr") } - Button { - text: qsTr("WORLD") - highlighted: app.activeQuickFilter === "world" - onClicked: openWorld() + Label { + text: qsTr("Sort") + color: BearTheme.textMuted + font.bold: true + font.pixelSize: 11 + width: 76 + height: 30 + verticalAlignment: Text.AlignVCenter } - Item { Layout.fillWidth: true } + Button { + width: 78 + height: 30 + text: qsTr("Name") + onClicked: { + if (app.backend && app.currentPage !== "favorites") { + app.backend.sortStations("name") + } + } + } + + 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 { visible: root.compactMode Layout.fillWidth: true @@ -187,4 +240,4 @@ ColumnLayout { } } } -} \ No newline at end of file +} diff --git a/src/qml/components/SearchToolbar.qml b/src/qml/components/SearchToolbar.qml index c55f1ab..c06b33c 100644 --- a/src/qml/components/SearchToolbar.qml +++ b/src/qml/components/SearchToolbar.qml @@ -21,12 +21,13 @@ ColumnLayout { RowLayout { visible: !root.compactMode Layout.fillWidth: true - spacing: 8 + spacing: 10 TextField { id: searchField Layout.fillWidth: true - placeholderText: qsTr("Search stations (name, genre, country)") + Layout.minimumWidth: 180 + placeholderText: qsTr("Search stations (name, genre, country)...") onTextChanged: { if (app.backend) { app.backend.filterQuery = text @@ -40,44 +41,35 @@ ColumnLayout { } } - Button { - text: qsTr("Search") - highlighted: true - onClicked: { - if (searchField.text.length < 2 || !app.backend) return - app.currentPage = "search" - app.backend.searchStations(searchField.text) - } - } + // Right Search and Clear buttons + RowLayout { + spacing: 6 - Button { - text: qsTr("Sort A-Z") - onClicked: { - if (app.backend && app.currentPage !== "favorites") { - app.backend.sortStations("name") + Button { + text: root.app.width < 1180 ? "🔍" : "🔍 " + qsTr("Search") + highlighted: true + Layout.preferredWidth: root.app.width < 1180 ? 44 : 88 + onClicked: { + if (searchField.text.length < 2 || !app.backend) return + app.currentPage = "search" + app.backend.searchStations(searchField.text) } } - } - 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") + Button { + text: root.app.width < 1180 ? "✕" : "✕ " + qsTr("Clear") + Layout.preferredWidth: root.app.width < 1180 ? 44 : 82 + onClicked: { + searchField.text = "" + if (app.backend) { + app.backend.filterQuery = "" + } } } } } + // Compact Mode (Mobile/Narrow) Search Layout ColumnLayout { visible: root.compactMode Layout.fillWidth: true @@ -108,59 +100,27 @@ ColumnLayout { Layout.fillWidth: true spacing: 8 - Item { Layout.fillWidth: true } - Button { - text: qsTr("Search") + text: "🔍 " + qsTr("Search") highlighted: true + Layout.fillWidth: true onClicked: { - if (compactSearchField.text.length < 2 || !app.backend) return + if (searchField.text.length < 2 || !app.backend) return app.currentPage = "search" - app.backend.searchStations(compactSearchField.text) - } - } - } - - Flow { - Layout.fillWidth: true - width: parent.width - spacing: 8 - - Button { - text: "A-Z" - onClicked: { - if (app.backend && app.currentPage !== "favorites") { - app.backend.sortStations("name") - } + app.backend.searchStations(searchField.text) } } Button { - text: "kb" + text: "✕ " + qsTr("Clear") + Layout.fillWidth: true onClicked: { - if (app.backend && app.currentPage !== "favorites") { - app.backend.sortStations("bitrate") - } - } - } - - Button { - text: "❤" - onClicked: { - if (app.backend && app.currentPage !== "favorites") { - app.backend.sortStations("votes") + searchField.text = "" + if (app.backend) { + app.backend.filterQuery = "" } } } } } - - 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 - } -} \ No newline at end of file +} diff --git a/src/qml/components/SidebarButton.qml b/src/qml/components/SidebarButton.qml new file mode 100644 index 0000000..2b62072 --- /dev/null +++ b/src/qml/components/SidebarButton.qml @@ -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 + } + } +} diff --git a/src/qml/components/SidebarNavigation.qml b/src/qml/components/SidebarNavigation.qml new file mode 100644 index 0000000..a99a90c --- /dev/null +++ b/src/qml/components/SidebarNavigation.qml @@ -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() + } + } + } + } + } +} \ No newline at end of file diff --git a/src/qml/components/StationCard.qml b/src/qml/components/StationCard.qml index 1a3c27c..32030d8 100644 --- a/src/qml/components/StationCard.qml +++ b/src/qml/components/StationCard.qml @@ -16,9 +16,9 @@ Rectangle { required property bool compactMode required property real listWidth - height: compactMode ? 72 : 78 + height: 52 width: listWidth - radius: 10 + radius: 0 readonly property bool isCurrent: { 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 color: isCurrent - ? (cardMouse.containsMouse ? "#1d3350" : "#16283e") - : (cardMouse.containsMouse ? BearTheme.cardHover : BearTheme.card) - border.color: isCurrent ? BearTheme.accent : BearTheme.cardBorder - border.width: isCurrent ? 2 : 1 + ? "#4a4a4d" + : (cardMouse.containsMouse ? "#2d2e34" : "transparent") + + 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() { if (!app.backend) return @@ -56,6 +70,7 @@ Rectangle { hoverEnabled: true acceptedButtons: Qt.LeftButton onClicked: { + app.selectedStation = root.modelData if (!app.backend) return if (root.isCurrent) { 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 { anchors.fill: parent - anchors.margins: 10 + anchors.leftMargin: 8 + anchors.rightMargin: 8 spacing: 10 + // Favicon Rect Rectangle { - Layout.preferredWidth: 44 - Layout.preferredHeight: 44 - radius: 8 - color: "#123154" - border.color: BearTheme.accent + Layout.preferredWidth: 34 + Layout.preferredHeight: 34 + radius: 5 + color: "#34353b" + clip: true Image { id: stationFavicon anchors.fill: parent - anchors.margins: 3 + anchors.margins: 2 source: (root.modelData.favicon && root.modelData.favicon.startsWith("https://")) ? root.modelData.favicon : "" @@ -100,9 +126,11 @@ Rectangle { } } + // Info Column ColumnLayout { Layout.fillWidth: true - spacing: 2 + spacing: 1 + Layout.alignment: Qt.AlignVCenter RowLayout { Layout.fillWidth: true @@ -113,10 +141,11 @@ Rectangle { text: root.modelData.name color: root.isCurrent ? BearTheme.accent : BearTheme.textMain font.bold: true - font.pixelSize: compactMode ? 13 : 14 + font.pixelSize: 13 elide: Text.ElideRight } + // Tiny EQ Indicator Row { id: eqAnimation spacing: 2 @@ -126,32 +155,26 @@ Rectangle { Rectangle { id: bar1 width: 2 - height: 12 - color: BearTheme.accent + height: 10 + color: BearTheme.playingAccent radius: 1 - Behavior on height { - NumberAnimation { duration: 120 } - } + Behavior on height { NumberAnimation { duration: 120 } } } Rectangle { id: bar2 width: 2 - height: 12 - color: BearTheme.accent + height: 10 + color: BearTheme.playingAccent radius: 1 - Behavior on height { - NumberAnimation { duration: 120 } - } + Behavior on height { NumberAnimation { duration: 120 } } } Rectangle { id: bar3 width: 2 - height: 12 - color: BearTheme.accent + height: 10 + color: BearTheme.playingAccent radius: 1 - Behavior on height { - NumberAnimation { duration: 120 } - } + Behavior on height { NumberAnimation { duration: 120 } } } Timer { @@ -159,17 +182,17 @@ Rectangle { running: root.isPlaying repeat: true onTriggered: { - bar1.height = Math.floor(Math.random() * 11) + 3 - bar2.height = Math.floor(Math.random() * 11) + 3 - bar3.height = Math.floor(Math.random() * 11) + 3 + bar1.height = Math.floor(Math.random() * 9) + 2 + bar2.height = Math.floor(Math.random() * 9) + 2 + bar3.height = Math.floor(Math.random() * 9) + 2 } } onVisibleChanged: { if (!visible) { - bar1.height = 12 - bar2.height = 12 - bar3.height = 12 + bar1.height = 10 + bar2.height = 10 + bar3.height = 10 } } } @@ -177,21 +200,20 @@ Rectangle { Label { Layout.fillWidth: true - text: root.modelData.country + " • " - + (root.modelData.codec && root.modelData.codec !== "unknown" && root.modelData.codec !== "" - ? root.modelData.codec.toUpperCase() + " • " : "") - + (root.modelData.bitrate > 0 ? root.modelData.bitrate + " kbps" : qsTr("Stream")) + text: root.metadataText color: BearTheme.textMuted font.pixelSize: 11 elide: Text.ElideRight } } + // Edit Button (Custom User Stations Only) Button { visible: !root.modelData.uuid - Layout.preferredWidth: compactMode ? 34 : 40 - Layout.preferredHeight: 40 + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 text: "✏" + flat: true ToolTip.visible: hovered ToolTip.text: qsTr("Edit Station") onClicked: { @@ -200,10 +222,13 @@ Rectangle { } } + // Favorite Button Button { - Layout.preferredWidth: compactMode ? 34 : 40 - Layout.preferredHeight: 40 + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 text: root.modelData.isFavorite ? "★" : "☆" + flat: true + font.pixelSize: 14 onClicked: { if (!app.backend) return app.backend.toggleFavoriteById(root.modelData.uuid, root.modelData.urlResolved) @@ -211,10 +236,13 @@ Rectangle { } } + // Play/Pause Button Button { - Layout.preferredWidth: compactMode ? 34 : 40 - Layout.preferredHeight: 40 + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 text: (root.isCurrent && app.backend && app.backend.player && app.backend.player.playing) ? "⏸" : "▶" + flat: true + font.pixelSize: 13 onClicked: { if (!app.backend) return if (root.isCurrent) { @@ -225,4 +253,4 @@ Rectangle { } } } -} \ No newline at end of file +} diff --git a/src/qml/components/StationDetailsPanel.qml b/src/qml/components/StationDetailsPanel.qml new file mode 100644 index 0000000..0148b9e --- /dev/null +++ b/src/qml/components/StationDetailsPanel.qml @@ -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 + } + } +} diff --git a/src/qml/components/StationListPanel.qml b/src/qml/components/StationListPanel.qml index 5ae17d1..5a41d31 100644 --- a/src/qml/components/StationListPanel.qml +++ b/src/qml/components/StationListPanel.qml @@ -26,7 +26,7 @@ Item { ListView { id: stationList width: stationScrollView.availableWidth - spacing: 8 + spacing: 0 model: app.activeModel() visible: count > 0 @@ -56,4 +56,4 @@ Item { color: BearTheme.textMuted } } -} \ No newline at end of file +} diff --git a/src/qml/components/qmldir b/src/qml/components/qmldir index 70e0b82..90e1946 100644 --- a/src/qml/components/qmldir +++ b/src/qml/components/qmldir @@ -5,9 +5,12 @@ StationCard 1.0 StationCard.qml WorldCategoryHeader 1.0 WorldCategoryHeader.qml WorldCategories 1.0 WorldCategories.qml PlayerBar 1.0 PlayerBar.qml -AboutDialog 1.0 AboutDialog.qml +AboutPage 1.0 AboutPage.qml AddStationDialog 1.0 AddStationDialog.qml EditStationDialog 1.0 EditStationDialog.qml LoadingOverlay 1.0 LoadingOverlay.qml ToastPopup 1.0 ToastPopup.qml -StationListPanel 1.0 StationListPanel.qml \ No newline at end of file +StationListPanel 1.0 StationListPanel.qml +SidebarNavigation 1.0 SidebarNavigation.qml +SidebarButton 1.0 SidebarButton.qml +StationDetailsPanel 1.0 StationDetailsPanel.qml diff --git a/src/qml/theme/BearTheme.qml b/src/qml/theme/BearTheme.qml index 2fe0fb4..63ac6ea 100644 --- a/src/qml/theme/BearTheme.qml +++ b/src/qml/theme/BearTheme.qml @@ -5,15 +5,17 @@ pragma Singleton import QtQuick 2.15 QtObject { - readonly property color bgA: "#0f141b" - readonly property color bgB: "#131b25" - readonly property color panel: "#182433" - readonly property color card: "#1b2a3d" - readonly property color cardHover: "#223654" - readonly property color cardBorder: "#2d4566" - readonly property color accent: "#2bb0ff" - readonly property color textMain: "#eaf1fb" - readonly property color textMuted: "#9eb1c9" + readonly property color bgA: "#1d1d22" + readonly property color bgB: "#202027" + readonly property color panel: "#22232a" + readonly property color sidebar: "#2b2c33" + readonly property color card: "#2a2b31" + readonly property color cardHover: "#33343b" + readonly property color cardBorder: "#3a3b43" + readonly property color accent: "#d7d7df" + readonly property color playingAccent: "#0a84ff" + readonly property color textMain: "#f0f0f4" + readonly property color textMuted: "#a3a4ad" readonly property color warn: "#ff8b8b" readonly property var worldTags: [ @@ -30,4 +32,4 @@ QtObject { { name: qsTr("Ambient"), tag: "ambient", icon: "🌌" }, { name: qsTr("Blues / Soul"), tag: "blues", icon: "🎺" } ] -} \ No newline at end of file +} diff --git a/tests/qrc_resources_test.cpp b/tests/qrc_resources_test.cpp index e37a33d..2c07fe3 100644 --- a/tests/qrc_resources_test.cpp +++ b/tests/qrc_resources_test.cpp @@ -24,7 +24,8 @@ void QrcResourcesTest::qmlModulesAreEmbedded() QVERIFY(QFile::exists(QStringLiteral(":/qml/theme/qmldir"))); QVERIFY(QFile::exists(QStringLiteral(":/qml/components/qmldir"))); QVERIFY(QFile::exists(QStringLiteral(":/assets/app/bearwave.png"))); + QVERIFY(QFile::exists(QStringLiteral(":/assets/legal/gpl-3.0.txt"))); } QTEST_APPLESS_MAIN(QrcResourcesTest) -#include "qrc_resources_test.moc" \ No newline at end of file +#include "qrc_resources_test.moc" diff --git a/translations/bearwave_de.ts b/translations/bearwave_de.ts index 82dbec3..bb21ef3 100644 --- a/translations/bearwave_de.ts +++ b/translations/bearwave_de.ts @@ -1,555 +1,875 @@ - - + - - AboutDialog - - - Version - - - - - build - - - - - BearWave - BearWave - - - - Internet Radio Player for KDE - Internet Radio Player für KDE - - - - Author: Sebastian Palencsár - Autor: Sebastian Palencsár - - - - Open website - Website öffnen - - - - Open GitHub - GitHub öffnen - - - - Open LinkedIn - LinkedIn öffnen - - - - GNU GPLv3 License - GNU GPLv3-Lizenz - - - - Copyright (c) 2026 - Copyright (c) 2026 - - - - AddStationDialog - - - Add station manually - Sender manuell hinzufügen - - - - Name - Name - - - - Stream URL (http/https) - Stream URL (http/https) - - - - Country (optional) - Land (optional) - - - - Station added - Sender hinzugefügt - - - - BearTheme - - - Pop - - - - - Rock - Rock - - - - Electronic - - - - - Classical - - - - - Jazz - Jazz - - - - Metal - - - - - Hip Hop - - - - - Chillout - - - - - News / Talk - - - - - Soundtracks - - - - - Ambient - - - - - Blues / Soul - - - - - EditStationDialog - - - Edit station - - - - - Name - Name - - - - Stream URL (http/https) - Stream URL (http/https) - - - - Country (optional) - Land (optional) - - - - Manual - Manuell - - - - Station updated - - - - - HeaderNavigation - - - - Top - Top - - - - - DE - DE - - - - - NL - NL - - - - - Favorites - Favoriten - - - - - History - Verlauf - - - - Manual + - Manuell + - - - - - About - About - - - - Resume - Fortsetzen - - - - LoadingOverlay - - - Loading stations... - Lade Sender... - - - - Main - - - BearWave - BearWave - - - - Error: - Fehler: - - - - Top Global - Weltweite Top-Sender - - - - NotificationManager - - - Station: %1 - - - - - PlayerBar - - - - No station selected - Kein Sender ausgewählt - - - - Now playing: - Jetzt läuft: - - - - Now playing: No track info - Jetzt läuft: Keine Titelinfo - - - - Mute - - - - - Unmute - - - - - QuickFilters - - - - Genre: - Genre: - - - - - Rock - Rock - - - - - News - News - - - - - Jazz - Jazz - - - - - Country: - Land: - - - - - US - US - - - - - UK - UK - - - - - FR - FR - - - - - WORLD - WELT - - - - RadioBackend - - - - Stream URL must use http:// or https:// - - - - - - Manual - Manuell - - - - - - Last played - Zuletzt gehört - - - - RadioBrowser - - - Network error - Netzwerkfehler - - - - SearchToolbar - - - - Search stations (name, genre, country) - Sender suchen (Name, Genre, Land) - - - - - Search - Suche - - - - Sort A-Z - Sort A-Z - - - - Sort Bitrate - Sort Bitrate - - - - Sort Votes - Sort Votes - - - - Tip: you are not limited to DE/NL. Search worldwide by country, genre, or station name. - Tipp: Du bist nicht auf DE/NL begrenzt. Suche weltweit nach Ländern, Genres oder Sendernamen. - - - - StationCard - - - Stream - Stream - - - - Edit Station - - - - - Removed from favorites - Aus Favoriten entfernt - - - - Added to favorites - Zu Favoriten hinzugefügt - - - - StationListPanel - - - No playback history - Kein Wiedergabeverlauf - - - - No stations loaded yet - Noch keine Sender geladen - - - - Play some stations to build history - Spiele Sender ab, um einen Verlauf aufzubauen - - - - Load DE/NL stations or use search - Lade DE/NL oder nutze die Suche - - - - SystemTrayManager - - - BearWave - BearWave - - - - Pause - Pause - - - - Play - Wiedergabe - - - - Hide - Verstecken - - - - Show - Zeigen - - - - Quit - Beenden - - - - WorldCategories - - - Explore World Stations - Weltweite Sender entdecken - - - - Filter countries... - Länder filtern... - - - - Choose a country or music style to find radio stations from all over the world. - Wähle ein Land oder eine Musikrichtung, um Radiosender aus der ganzen Welt zu finden. - - - - Countries - Länder - - - - Filtered Countries - Gefilterte Länder - - - - Top 200 - - - - - stations - Sender - - - - Popular Music Styles - Beliebte Musikrichtungen - - - - WorldCategoryHeader - - - ← Back to Categories - ← Zurück zu den Kategorien - - - - World > Country: - Welt > Land: - - - - World > Genre: - Welt > Genre: - - - + + AboutDialog + + BearWave + BearWave + + + Internet Radio Player for KDE + Internet Radio Player für KDE + + + Author: Sebastian Palencsár + Autor: Sebastian Palencsár + + + Open website + Website öffnen + + + Open GitHub + GitHub öffnen + + + Open LinkedIn + LinkedIn öffnen + + + GNU GPLv3 License + GNU GPLv3-Lizenz + + + Copyright (c) 2026 + Copyright (c) 2026 + + + + AboutPage + + + Version + Version + + + + Build + Build + + + + Qt 6 + Qt 6 + + + + Application framework and UI runtime: Core, DBus, Network, Quick, Quick Controls 2, Widgets, Multimedia, and LinguistTools. + Anwendungsframework und UI-Laufzeit: Core, DBus, Network, Quick, Quick Controls 2, Widgets, Multimedia und LinguistTools. + + + + Subject to the upstream licenses of the installed Qt distribution. + Es gelten die Upstream-Lizenzen der installierten Qt-Distribution. + + + + Qt Multimedia backend + Qt-Multimedia-Backend + + + + Audio playback runs through Qt Multimedia and the backend available on the system, such as GStreamer or FFmpeg. + Die Audiowiedergabe läuft über Qt Multimedia und das auf dem System verfügbare Backend, zum Beispiel GStreamer oder FFmpeg. + + + + Backend components are provided by the operating system or Qt build and retain their own licenses. + Backend-Komponenten werden vom Betriebssystem oder Qt-Build bereitgestellt und behalten ihre eigenen Lizenzen. + + + + Radio Browser API + Radio Browser API + + + + Public station directory API for station data and stream discovery. BearWave does not bundle any Radio Browser server code. + Öffentliche Senderverzeichnis-API für Senderdaten und Stream-Erkennung. BearWave bündelt keinen Radio-Browser-Servercode. + + + + External service or API; subject to the upstream project and service terms. + Externer Dienst bzw. externe API; es gelten die Bedingungen des Upstream-Projekts und Dienstes. + + + + freedesktop.org integrations + freedesktop.org-Integrationen + + + + D-Bus, MPRIS, desktop notifications, desktop entry, and AppStream metadata. + D-Bus, MPRIS, Desktop-Benachrichtigungen, Desktop-Datei und AppStream-Metadaten. + + + + Uses standards and interfaces for desktop integration; no third-party implementation is bundled. + Genutzte Standards und Schnittstellen für Desktop-Integration; es wird keine fremde Implementierung gebündelt. + + + + Internet radio player for KDE + Internetradio-Player für KDE + + + + Author + Autor + + + + Sebastian Palencsár + Sebastian Palencsár + + + + Open website + Website öffnen + + + + Open GitHub + GitHub öffnen + + + + Open LinkedIn + LinkedIn öffnen + + + + Technologies and third-party components + Technologien und Drittkomponenten + + + + BearWave uses the following frameworks, services, and desktop standards. These components are not relicensed by BearWave. + BearWave nutzt die folgenden Frameworks, Dienste und Desktop-Standards. Diese Komponenten werden nicht durch BearWave neu lizenziert. + + + + GNU GPLv3 License + GNU GPLv3-Lizenz + + + + Copyright (c) 2026 + Copyright (c) 2026 + + + + License text could not be loaded. + Lizenztext konnte nicht geladen werden. + + + + AddStationDialog + + + Add station manually + Sender manuell hinzufügen + + + + Name + Name + + + + Stream URL (http/https) + Stream URL (http/https) + + + + Country (optional) + Land (optional) + + + + Station added + Sender hinzugefügt + + + + BearTheme + + + Pop + Pop + + + + Rock + Rock + + + + Electronic + Elektronisch + + + + Classical + Klassik + + + + Jazz + Jazz + + + + Metal + Metal + + + + Hip Hop + Hip Hop + + + + Chillout + Chillout + + + + News / Talk + News / Talk + + + + Soundtracks + Soundtracks + + + + Ambient + Ambient + + + + Blues / Soul + Blues / Soul + + + + EditStationDialog + + + Edit station + Sender bearbeiten + + + + Name + Name + + + + Stream URL (http/https) + Stream URL (http/https) + + + + Country (optional) + Land (optional) + + + + Manual + Manuell + + + + Station updated + Sender aktualisiert + + + + HeaderNavigation + + + Top + Top + + + + DE + DE + + + + NL + NL + + + + Favorites + Favoriten + + + + History + Verlauf + + + Manual + + Manuell + + + + + About + About + + + Resume + Fortsetzen + + + + LoadingOverlay + + + Loading stations... + Lade Sender... + + + + Main + + + BearWave + BearWave + + + + Error: + Fehler: + + + + Top Global + Weltweite Top-Sender + + + + PlayerBar + + + + No station selected + Kein Sender ausgewählt + + + + Now playing: + Jetzt läuft: + + + + Live radio + Live-Radio + + + Now playing: No track info + Jetzt läuft: Keine Titelinfo + + + + Mute + Stummschalten + + + + Unmute + Ton einschalten + + + + QuickFilters + + + Genre: + Genre: + + + + + Rock + Rock + + + + + News + News + + + + + Jazz + Jazz + + + + GB + GB + + + + Name + Name + + + + Bitrate + Bitrate + + + + Country: + Land: + + + + + US + US + + + + Quick access + Schnellzugriff + + + + Sort + Sortierung + + + + Votes + Stimmen + + + + UK + UK + + + + + FR + FR + + + + WORLD + WELT + + + + RadioBackend + + Manual + Manuell + + + Last played + Zuletzt gehört + + + + RadioBrowser + + Network error + Netzwerkfehler + + + + SearchToolbar + + + Search stations (name, genre, country)... + Sender suchen (Name, Genre, Land)... + + + + + Clear + Löschen + + + + Search stations (name, genre, country) + Sender suchen (Name, Genre, Land) + + + + + Search + Suche + + + Sort A-Z + Sort A-Z + + + Sort Bitrate + Sort Bitrate + + + Sort Votes + Sort Votes + + + Tip: you are not limited to DE/NL. Search worldwide by country, genre, or station name. + Tipp: Du bist nicht auf DE/NL begrenzt. Suche weltweit nach Ländern, Genres oder Sendernamen. + + + + SidebarNavigation + + + Stations + Sender + + + + Top Global + Weltweite Top-Sender + + + + Germany + Deutschland + + + + Netherlands + Niederlande + + + + Library + Mediathek + + + + Favorites + Favoriten + + + + History + Verlauf + + + + Worldwide + Weltweit + + + Search + Suche + + + STATIONS + SENDER + + + LIBRARY + MEDIATHEK + + + + Add Station + Sender hinzufügen + + + + About BearWave + Über BearWave + + + + Resume + Fortsetzen + + + + StationCard + + + votes + Stimmen + + + + Stream + Stream + + + + Edit Station + Sender bearbeiten + + + + Removed from favorites + Aus Favoriten entfernt + + + + Added to favorites + Zu Favoriten hinzugefügt + + + + StationDetailsPanel + + + Stream URL copied! + Stream-URL kopiert! + + + + Details + Details + + + Pause + Pause + + + Play + Abspielen + + + Favorite + Favorit + + + Add + Hinzufügen + + + + Pause + Pause + + + + Play + Abspielen + + + + Favorite + Favorit + + + + Add + Hinzufügen + + + + Removed from favorites + Aus Favoriten entfernt + + + + Added to favorites + Zu Favoriten hinzugefügt + + + + Groups + Gruppen + + + + Groups coming soon + Gruppen-Feature in Kürze verfügbar! + + + + Tags + Tags + + + + Links + Links + + + TECHNICAL DETAILS + TECHNISCHE DETAILS + + + + Codec: + Codec: + + + + + Unknown + Unbekannt + + + + Bitrate: + Bitrate: + + + + Votes: + Stimmen (Votes): + + + GENRES / TAGS + GENRES / TAGS + + + LINKS & ACTIONS + LINKS & AKTIONEN + + + + Open homepage + Homepage öffnen + + + + Copy stream URL + Stream-URL kopieren + + + + No station selected + Kein Sender ausgewählt + + + + Select a station from the list +to view details. + Wähle einen Sender aus der Liste, +um Details anzuzeigen. + + + + StationListPanel + + + No playback history + Kein Wiedergabeverlauf + + + + No stations loaded yet + Noch keine Sender geladen + + + + Play some stations to build history + Spiele Sender ab, um einen Verlauf aufzubauen + + + + Load DE/NL stations or use search + Lade DE/NL oder nutze die Suche + + + + SystemTrayManager + + BearWave + BearWave + + + Pause + Pause + + + Play + Wiedergabe + + + Hide + Verstecken + + + Show + Zeigen + + + Quit + Beenden + + + + WorldCategories + + + Explore World Stations + Weltweite Sender entdecken + + + + Filter countries... + Länder filtern... + + + + Choose a country or music style to find radio stations from all over the world. + Wähle ein Land oder eine Musikrichtung, um Radiosender aus der ganzen Welt zu finden. + + + + Countries + Länder + + + + Filtered Countries + Gefilterte Länder + + + + Top 200 + Top 200 + + + + stations + Sender + + + + Popular Music Styles + Beliebte Musikrichtungen + + + + WorldCategoryHeader + + + ← Back to Categories + ← Zurück zu den Kategorien + + + + World > Country: + Welt > Land: + + + + World > Genre: + Welt > Genre: + + + \ No newline at end of file diff --git a/translations/bearwave_ru.ts b/translations/bearwave_ru.ts index 3c71201..e13d5a7 100644 --- a/translations/bearwave_ru.ts +++ b/translations/bearwave_ru.ts @@ -1,555 +1,858 @@ - - + - - AboutDialog - - - Version - Версия - - - - build - сборка - - - - BearWave - BearWave - - - - Internet Radio Player for KDE - Интернет радиоплеер для KDE - - - - Author: Sebastian Palencsár - Автор: Sebastian Palencsár - - - - Open website - Перейти на веб-сайт - - - - Open GitHub - Перейти на GitHub - - - - Open LinkedIn - Перейти на LinkedIn - - - - GNU GPLv3 License - Лицензия GNU GPLv3 - - - - Copyright (c) 2026 - Copyright (c) 2026 - - - - AddStationDialog - - - Add station manually - Добавить станцию вручную - - - - Name - Название - - - - Stream URL (http/https) - Адрес канала (http/https) - - - - Country (optional) - Страна (необязательно) - - - - Station added - Станция добавлена - - - - BearTheme - - - Pop - Поп - - - - Rock - Рок - - - - Electronic - Электронная - - - - Classical - Классическая - - - - Jazz - Джаз - - - - Metal - Метал - - - - Hip Hop - Хип-хоп - - - - Chillout - Успокаивающая - - - - News / Talk - Новости / Разговоры - - - - Soundtracks - Саундтреки - - - - Ambient - Атмосфера - - - - Blues / Soul - Блюз / Соул - - - - EditStationDialog - - - Edit station - Редактировать станцию - - - - Name - Название - - - - Stream URL (http/https) - Адрес канала (http/https) - - - - Country (optional) - Страна (необязательно) - - - - Manual - Справка - - - - Station updated - Обновление станции - - - - HeaderNavigation - - - - Top - Топ - - - - - DE - DE - - - - - NL - NL - - - - - Favorites - Избранные - - - - - History - История - - - - Manual + - Вручную + - - - - - About - О BearWave - - - - Resume - Продолжить - - - - LoadingOverlay - - - Loading stations... - Загрузка станций... - - - - Main - - - BearWave - BearWave - - - - Error: - Ошибка: - - - - Top Global - Всемирный топ - - - - NotificationManager - - - Station: %1 - Станция: %1 - - - - PlayerBar - - - - No station selected - Нет выбранной станции - - - - Now playing: - Сейчас играет: - - - - Now playing: No track info - Сейчас играет: нет информации о треке - - - - Mute - Без звука - - - - Unmute - Со звуком - - - - QuickFilters - - - - Genre: - Жанр: - - - - - Rock - Рок - - - - - News - Новости - - - - - Jazz - Джаз - - - - - Country: - Страна: - - - - - US - US - - - - - UK - UK - - - - - FR - FR - - - - - WORLD - Мир - - - - RadioBackend - - - - Stream URL must use http:// or https:// - URL-адрес потока должен использовать http:// или https:// - - - - - Manual - Справка - - - - - - Last played - Последнее - - - - RadioBrowser - - - Network error - Ошибка сети - - - - SearchToolbar - - - - Search stations (name, genre, country) - Поиск станций (название, жанр, страна) - - - - - Search - Поиск - - - - Sort A-Z - Сорт. А-Я - - - - Sort Bitrate - Сорт. битрейт - - - - Sort Votes - Сорт. голоса - - - - Tip: you are not limited to DE/NL. Search worldwide by country, genre, or station name. - Совет: вы не ограничены DE/NL. Ищите по всему миру, по стране, жанру или названию станции. - - - - StationCard - - - Stream - Поток - - - - Edit Station - Редактировать станцию - - - - Removed from favorites - Убрать из избранного - - - - Added to favorites - Добавить в избранное - - - - StationListPanel - - - No playback history - Нет истории воспроизведения - - - - No stations loaded yet - Станции пока не загружены - - - - Play some stations to build history - Прослушайте несколько станций, чтобы создать историю - - - - Load DE/NL stations or use search - Загрузите станции DE/NL или воспользуйтесь поиском - - - - SystemTrayManager - - - BearWave - BearWave - - - - Pause - Пауза - - - - Play - Слушать - - - - Hide - Скрыть - - - - Show - Показать - - - - Quit - Выйти - - - - WorldCategories - - - Explore World Stations - Сканировать мировые станции - - - - Filter countries... - Фильтровать страны... - - - - Choose a country or music style to find radio stations from all over the world. - Выберите страну или музыкальный стиль, чтобы найти радиостанции со всего мира. - - - - Countries - Страны - - - - Filtered Countries - Выбранные страны - - - - Top 200 - Топ 200 - - - - stations - станции - - - - Popular Music Styles - Популярные музыкальные стили - - - - WorldCategoryHeader - - - ← Back to Categories - ← Вернуться к категориям - - - - World > Country: - Мир > Страна: - - - - World > Genre: - Мир > Жанр: - - - + + AboutDialog + + Version + Версия + + + build + сборка + + + BearWave + BearWave + + + Internet Radio Player for KDE + Интернет радиоплеер для KDE + + + Author: Sebastian Palencsár + Автор: Sebastian Palencsár + + + Open website + Перейти на веб-сайт + + + Open GitHub + Перейти на GitHub + + + Open LinkedIn + Перейти на LinkedIn + + + GNU GPLv3 License + Лицензия GNU GPLv3 + + + Copyright (c) 2026 + Copyright (c) 2026 + + + + AboutPage + + + Version + Версия + + + + Build + Сборка + + + + Qt 6 + Qt 6 + + + + Application framework and UI runtime: Core, DBus, Network, Quick, Quick Controls 2, Widgets, Multimedia, and LinguistTools. + Прикладной фреймворк и среда выполнения UI: Core, DBus, Network, Quick, Quick Controls 2, Widgets, Multimedia и LinguistTools. + + + + Subject to the upstream licenses of the installed Qt distribution. + Действуют лицензии upstream-версии установленного дистрибутива Qt. + + + + Qt Multimedia backend + Мультимедийный бэкенд Qt + + + + Audio playback runs through Qt Multimedia and the backend available on the system, such as GStreamer or FFmpeg. + Воспроизведение аудио выполняется через Qt Multimedia и доступный в системе бэкенд, например GStreamer или FFmpeg. + + + + Backend components are provided by the operating system or Qt build and retain their own licenses. + Компоненты бэкенда предоставляются операционной системой или сборкой Qt и сохраняют свои собственные лицензии. + + + + Radio Browser API + Radio Browser API + + + + Public station directory API for station data and stream discovery. BearWave does not bundle any Radio Browser server code. + Публичный API каталога станций для данных о станциях и обнаружения потоков. BearWave не включает серверный код Radio Browser. + + + + External service or API; subject to the upstream project and service terms. + Внешний сервис или API; действуют условия upstream-проекта и сервиса. + + + + freedesktop.org integrations + Интеграции freedesktop.org + + + + D-Bus, MPRIS, desktop notifications, desktop entry, and AppStream metadata. + D-Bus, MPRIS, уведомления рабочего стола, desktop-файл и метаданные AppStream. + + + + Uses standards and interfaces for desktop integration; no third-party implementation is bundled. + Используются стандарты и интерфейсы для интеграции с рабочим столом; сторонние реализации не включаются. + + + + Internet radio player for KDE + Интернет-радиоплеер для KDE + + + + Author + Автор + + + + Sebastian Palencsár + Себастьян Паленчар + + + + Open website + Перейти на веб-сайт + + + + Open GitHub + Перейти на GitHub + + + + Open LinkedIn + Перейти на LinkedIn + + + + Technologies and third-party components + Технологии и сторонние компоненты + + + + BearWave uses the following frameworks, services, and desktop standards. These components are not relicensed by BearWave. + BearWave использует следующие фреймворки, сервисы и стандарты рабочего стола. Эти компоненты не перелицензируются BearWave. + + + + GNU GPLv3 License + Лицензия GNU GPLv3 + + + + Copyright (c) 2026 + Copyright (c) 2026 + + + + License text could not be loaded. + Не удалось загрузить текст лицензии. + + + + AddStationDialog + + + Add station manually + Добавить станцию вручную + + + + Name + Название + + + + Stream URL (http/https) + Адрес канала (http/https) + + + + Country (optional) + Страна (необязательно) + + + + Station added + Станция добавлена + + + + BearTheme + + + Pop + Поп + + + + Rock + Рок + + + + Electronic + Электронная + + + + Classical + Классическая + + + + Jazz + Джаз + + + + Metal + Метал + + + + Hip Hop + Хип-хоп + + + + Chillout + Успокаивающая + + + + News / Talk + Новости / Разговоры + + + + Soundtracks + Саундтреки + + + + Ambient + Атмосфера + + + + Blues / Soul + Блюз / Соул + + + + EditStationDialog + + + Edit station + Редактировать станцию + + + + Name + Название + + + + Stream URL (http/https) + Адрес канала (http/https) + + + + Country (optional) + Страна (необязательно) + + + + Manual + Справка + + + + Station updated + Обновление станции + + + + HeaderNavigation + + + Top + Топ + + + + DE + DE + + + + NL + NL + + + + Favorites + Избранные + + + + History + История + + + Manual + + Вручную + + + + + About + О BearWave + + + Resume + Продолжить + + + + LoadingOverlay + + + Loading stations... + Загрузка станций... + + + + Main + + + BearWave + BearWave + + + + Error: + Ошибка: + + + + Top Global + Всемирный топ + + + + NotificationManager + + Station: %1 + Станция: %1 + + + + PlayerBar + + + + No station selected + Нет выбранной станции + + + + Now playing: + Сейчас играет: + + + + Live radio + Прямой эфир + + + Now playing: No track info + Сейчас играет: нет информации о треке + + + + Mute + Без звука + + + + Unmute + Со звуком + + + + QuickFilters + + + Genre: + Жанр: + + + + + Rock + Рок + + + + + News + Новости + + + + + Jazz + Джаз + + + + GB + GB + + + + Name + Название + + + + Bitrate + Битрейт + + + + Country: + Страна: + + + + + US + US + + + + Quick access + Быстрый доступ + + + + Sort + Сортировка + + + + Votes + Голоса + + + + UK + UK + + + + + FR + FR + + + + WORLD + Мир + + + + RadioBackend + + Stream URL must use http:// or https:// + URL-адрес потока должен использовать http:// или https:// + + + Manual + Справка + + + Last played + Последнее + + + + RadioBrowser + + Network error + Ошибка сети + + + + SearchToolbar + + + Search stations (name, genre, country)... + Поиск станций (название, жанр, страна)... + + + + + Clear + Очистить + + + + Search stations (name, genre, country) + Поиск станций (название, жанр, страна) + + + + + Search + Поиск + + + Sort A-Z + Сорт. А-Я + + + Sort Bitrate + Сорт. битрейт + + + Sort Votes + Сорт. голоса + + + Tip: you are not limited to DE/NL. Search worldwide by country, genre, or station name. + Совет: вы не ограничены DE/NL. Ищите по всему миру, по стране, жанру или названию станции. + + + + SidebarNavigation + + + Stations + Станции + + + + Top Global + Всемирный топ + + + + Germany + Германия + + + + Netherlands + Нидерланды + + + + Library + Библиотека + + + + Favorites + Избранные + + + + History + История + + + + Worldwide + Весь мир + + + Search + Поиск + + + + Add Station + Добавить станцию + + + + About BearWave + О BearWave + + + + Resume + Продолжить + + + + StationCard + + + votes + голосов + + + + Stream + Поток + + + + Edit Station + Редактировать станцию + + + + Removed from favorites + Удалено из избранного + + + + Added to favorites + Добавлено в избранное + + + + StationDetailsPanel + + + Stream URL copied! + URL потока скопирован! + + + + Details + Подробности + + + + Pause + Пауза + + + + Play + Слушать + + + + Favorite + Избранное + + + + Add + Добавить + + + + Removed from favorites + Удалено из избранного + + + + Added to favorites + Добавлено в избранное + + + + Groups + Группы + + + + Groups coming soon + Группы скоро появятся + + + + Tags + Теги + + + + Links + Ссылки + + + + Codec: + Кодек: + + + + + Unknown + Неизвестно + + + + Bitrate: + Битрейт: + + + + Votes: + Голоса: + + + + Open homepage + Открыть сайт + + + + Copy stream URL + Копировать URL потока + + + + No station selected + Нет выбранной станции + + + + Select a station from the list +to view details. + Выберите станцию из списка, +чтобы просмотреть подробности. + + + + StationListPanel + + + No playback history + Нет истории воспроизведения + + + + No stations loaded yet + Станции пока не загружены + + + + Play some stations to build history + Прослушайте несколько станций, чтобы создать историю + + + + Load DE/NL stations or use search + Загрузите станции DE/NL или воспользуйтесь поиском + + + + SystemTrayManager + + BearWave + BearWave + + + Pause + Пауза + + + Play + Слушать + + + Hide + Скрыть + + + Show + Показать + + + Quit + Выйти + + + + WorldCategories + + + Explore World Stations + Сканировать мировые станции + + + + Filter countries... + Фильтровать страны... + + + + Choose a country or music style to find radio stations from all over the world. + Выберите страну или музыкальный стиль, чтобы найти радиостанции со всего мира. + + + + Countries + Страны + + + + Filtered Countries + Выбранные страны + + + + Top 200 + Топ 200 + + + + stations + станции + + + + Popular Music Styles + Популярные музыкальные стили + + + + WorldCategoryHeader + + + ← Back to Categories + ← Вернуться к категориям + + + + World > Country: + Мир > Страна: + + + + World > Genre: + Мир > Жанр: + + + \ No newline at end of file