diff --git a/Cargo.lock b/Cargo.lock
index dd36dc98..c102175c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -211,30 +211,6 @@ dependencies = [
"syn",
]
-[[package]]
-name = "atk"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a83b21d2aa75e464db56225e1bda2dd5993311ba1095acaa8fa03d1ae67026ba"
-dependencies = [
- "atk-sys",
- "bitflags",
- "glib",
- "libc",
-]
-
-[[package]]
-name = "atk-sys"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "badcf670157c84bb8b1cf6b5f70b650fed78da2033c9eed84c4e49b11cbe83ea"
-dependencies = [
- "glib-sys",
- "gobject-sys",
- "libc",
- "system-deps",
-]
-
[[package]]
name = "atomic-waker"
version = "1.0.0"
@@ -885,22 +861,6 @@ dependencies = [
"slab",
]
-[[package]]
-name = "gdk"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "679e22651cd15888e7acd01767950edca2ee9fcd6421fbf5b3c3b420d4e88bb0"
-dependencies = [
- "bitflags",
- "cairo-rs",
- "gdk-pixbuf",
- "gdk-sys",
- "gio",
- "glib",
- "libc",
- "pango",
-]
-
[[package]]
name = "gdk-pixbuf"
version = "0.14.0"
@@ -927,19 +887,35 @@ dependencies = [
]
[[package]]
-name = "gdk-sys"
-version = "0.14.0"
+name = "gdk4"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce41092cc569129a0afa34926e6dd1cf8411e25652d87febdea36859f7ff7ba"
+dependencies = [
+ "bitflags",
+ "cairo-rs",
+ "gdk-pixbuf",
+ "gdk4-sys",
+ "gio",
+ "glib",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "gdk4-sys"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e091b3d3d6696949ac3b3fb3c62090e5bfd7bd6850bef5c3c5ea701de1b1f1e"
+checksum = "ce39c71861b5bcde319fd4711a74e1bd6f4f474911170d51096597fef0b56011"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
"gio-sys",
"glib-sys",
"gobject-sys",
+ "graphene-sys",
"libc",
"pango-sys",
- "pkg-config",
"system-deps",
]
@@ -1034,26 +1010,6 @@ dependencies = [
"winapi",
]
-[[package]]
-name = "gladis"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ed050efc446f5350bbd26a3ffaf5ba1569f754a5ee5f0cb4b772e759a183327"
-dependencies = [
- "gladis_proc_macro",
- "gtk",
-]
-
-[[package]]
-name = "gladis_proc_macro"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53987c930e0b3a52e33148d4f5f3cddc8019573139162155b59f85aa84abd264"
-dependencies = [
- "quote",
- "syn",
-]
-
[[package]]
name = "glib"
version = "0.14.2"
@@ -1129,54 +1085,92 @@ dependencies = [
]
[[package]]
-name = "gtk"
+name = "graphene-rs"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1460a39f06e491e6112f27e71e51435c833ba370723224dd1743dfd1f201f19"
+dependencies = [
+ "glib",
+ "graphene-sys",
+ "libc",
+]
+
+[[package]]
+name = "graphene-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10ae864e5eab8bc8b6b8544ed259eb02dd61b25323b20e777a77aa289c05fd0c"
+checksum = "e7d23fb7a9547e5f072a7e0cd49cd648fedeb786d122b106217511980cbb8962"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "pkg-config",
+ "system-deps",
+]
+
+[[package]]
+name = "gsk4"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64932b730eaad3340378a03d633616eeed6d6705b59b81c9f579c88be8932475"
dependencies = [
- "atk",
"bitflags",
"cairo-rs",
- "field-offset",
- "futures-channel",
- "gdk",
- "gdk-pixbuf",
- "gio",
+ "gdk4",
"glib",
- "gtk-sys",
- "gtk3-macros",
+ "graphene-rs",
+ "gsk4-sys",
"libc",
- "once_cell",
"pango",
- "pkg-config",
]
[[package]]
-name = "gtk-sys"
-version = "0.14.0"
+name = "gsk4-sys"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c14c8d3da0545785a7c5a120345b3abb534010fb8ae0f2ef3f47c027fba303e"
+checksum = "685ffc776bedd91d68f47b41239525778b669432889721d7050d045270549b9a"
dependencies = [
- "atk-sys",
"cairo-sys-rs",
- "gdk-pixbuf-sys",
- "gdk-sys",
- "gio-sys",
+ "gdk4-sys",
"glib-sys",
"gobject-sys",
+ "graphene-sys",
"libc",
"pango-sys",
"system-deps",
]
[[package]]
-name = "gtk3-macros"
-version = "0.14.0"
+name = "gtk4"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c49e0311dac847a8ebc05e31f5c44c596314ee3b16c5f638ccfe24086d24bf1b"
+dependencies = [
+ "bitflags",
+ "cairo-rs",
+ "field-offset",
+ "futures-channel",
+ "gdk-pixbuf",
+ "gdk4",
+ "gio",
+ "glib",
+ "graphene-rs",
+ "gsk4",
+ "gtk4-macros",
+ "gtk4-sys",
+ "libc",
+ "once_cell",
+ "pango",
+]
+
+[[package]]
+name = "gtk4-macros"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21de1da96dc117443fb03c2e270b2d34b7de98d0a79a19bbb689476173745b79"
+checksum = "bbe4b77996bcf1ef20208c00043edda854ca2091b4be5e6a7c367f0f3846fa67"
dependencies = [
"anyhow",
"heck",
+ "itertools",
"proc-macro-crate 1.0.0",
"proc-macro-error",
"proc-macro2",
@@ -1184,6 +1178,25 @@ dependencies = [
"syn",
]
+[[package]]
+name = "gtk4-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3737e91619cf4257d8a07834f7a2c035d4daeaf9ad8e3958e56b2c411dbdca18"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gdk4-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "graphene-sys",
+ "gsk4-sys",
+ "libc",
+ "pango-sys",
+ "system-deps",
+]
+
[[package]]
name = "hashbrown"
version = "0.9.1"
@@ -1528,47 +1541,44 @@ dependencies = [
]
[[package]]
-name = "libc"
-version = "0.2.94"
+name = "libadwaita"
+version = "0.1.0-alpha-2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
-
-[[package]]
-name = "libhandy"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bcf9c79ec810a62f442ffd568d2de233983dc91c160abee4949b67a647024ed"
+checksum = "d117dc3147a6e5917a4652f638cfcb512edf5d29de2af6a8e30e0d5de4f7395b"
dependencies = [
- "bitflags",
- "gdk",
"gdk-pixbuf",
+ "gdk4",
"gio",
"glib",
- "gtk",
- "lazy_static",
+ "gtk4",
+ "libadwaita-sys",
"libc",
- "libhandy-sys",
"pango",
]
[[package]]
-name = "libhandy-sys"
-version = "0.8.0"
+name = "libadwaita-sys"
+version = "0.1.0-alpha-2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1938b93a8f29417992c452b7f43e7eff8a9f8d25b7f0bc923ae9d75b50a9cde3"
+checksum = "64f1631562bdc3061b757290f17ea23237d59354ff6907069f40e1366fdb1426"
dependencies = [
"gdk-pixbuf-sys",
- "gdk-sys",
+ "gdk4-sys",
"gio-sys",
"glib-sys",
"gobject-sys",
- "gtk-sys",
+ "gtk4-sys",
"libc",
"pango-sys",
- "pkg-config",
"system-deps",
]
+[[package]]
+name = "libc"
+version = "0.2.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
+
[[package]]
name = "libloading"
version = "0.5.2"
@@ -2969,16 +2979,15 @@ dependencies = [
"async-std",
"form_urlencoded",
"futures",
- "gdk",
"gdk-pixbuf",
+ "gdk4",
"gettext-rs",
"gio",
- "gladis",
"glib",
- "gtk",
+ "gtk4",
"isahc",
"lazy_static",
- "libhandy",
+ "libadwaita",
"librespot",
"protobuf",
"rand 0.8.4",
diff --git a/Cargo.toml b/Cargo.toml
index 44e3116d..aab3cfcd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,16 +4,13 @@ version = "0.1.16"
edition = "2018"
license = "MIT"
-[features]
-warn-cache = []
-
[dependencies.gtk]
-version = "^0.14.0"
-features = ["v3_24"]
+version = "^0.2.0"
+package = "gtk4"
[dependencies.gdk]
-version = "^0.14.0"
-features = ["v3_24"]
+version = "^0.2.0"
+package = "gdk4"
[dependencies.gio]
version = "^0.14.0"
@@ -61,8 +58,7 @@ features = ["gettext-system"]
[dependencies]
secret-service = "^2.0.1"
gdk-pixbuf = "^0.14.0"
-libhandy = "^0.8.0"
-gladis = "^1.0.0"
+libadwaita = "0.1.0-alpha-2"
ref_filter_map = "^1.0.1"
regex = "^1.4.6"
async-std = "^1.9.0"
diff --git a/README.md b/README.md
index 36841e7b..04ef5052 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,6 @@ Based on [librespot](https://github.com/librespot-org/librespot/).

-[Older demo gif](./demo.gif)
-
## Installing
| Package | Maintainer | Repo |
@@ -17,11 +15,6 @@ Based on [librespot](https://github.com/librespot-org/librespot/).
|
| dpeukert | https://gitlab.com/dpeukert/pkgbuilds/tree/main/spot-client |
-## GTK4
-
-**The GTK4 port is almost ready :) all future development and contributions should ideally target the [gtk4/main](https://github.com/xou816/spot/tree/gtk4/main) branch!**
-
-
## Usage notes
### Credentials
@@ -77,7 +70,7 @@ See [this comment](https://github.com/xou816/spot/issues/209#issuecomment-860180
- playlist management (creation and edition)
- liked tracks
- GNOME search provider?
-- improved search? (track results, )
+- improved search? (track results)
- recommendations?
## Contributing
@@ -105,17 +98,15 @@ If you can't build Spot locally, you may run the `spot-snapshots` action against
### With GNOME Builder and flatpak
-Pre-requisite: install the `org.freedesktop.Sdk.Extension.rust-stable` SDK extension with flatpak. Builder might do this for you automatically, but it will install an older version; make sure the version installed matches the version of the Freedesktop SDK GNOME uses (at the time of writing: 20.08).
+Pre-requisite: install the `org.freedesktop.Sdk.Extension.rust-stable` SDK extension with flatpak. Builder might do this for you automatically, but it will install an older version; make sure the version installed matches the version of the Freedesktop SDK GNOME uses.
Open the project in GNOME Builder and make the `dev.alextren.Spot.development.json` configuration active. Then build :)
### Manually
-Requires Rust (stable), GTK3, and a couple other things. Also requires libhandy1: it is not packaged on all distros at the moment, you might have to build it yourself!
-
-**Build** dependencies on Ubuntu 20.04 for instance: ```build-essential pkg-config meson libssl-dev libglib2.0-dev-bin libgtk-3-dev libasound2-dev libpulse-dev```.
+Requires Rust (stable), **GTK4**, and a couple other things. Also requires **libadwaita**: it is not packaged on all distros at the moment, you might have to build it yourself!
-Then, with meson:
+With meson:
```
meson target -Dbuildtype=debug -Doffline=false --prefix="$HOME/.local"
diff --git a/build-aux/clippy.sh b/build-aux/clippy.sh
index 70f0ab65..f60004bf 100644
--- a/build-aux/clippy.sh
+++ b/build-aux/clippy.sh
@@ -8,4 +8,4 @@ if [[ $OFFLINE = "true" ]]; then
export CARGO_HOME="$SRC"/cargo
fi
-cargo clippy --manifest-path "$SRC"/Cargo.toml -- -D warnings -A clippy::module_inception
+cargo clippy --manifest-path "$SRC"/Cargo.toml -- -D warnings -A clippy::module_inception -A clippy::new_without_default
diff --git a/cargo-sources.json b/cargo-sources.json
index 220df21b..ed0280bc 100644
--- a/cargo-sources.json
+++ b/cargo-sources.json
@@ -233,32 +233,6 @@
"dest": "cargo/vendor/async-trait-0.1.42",
"dest-filename": ".cargo-checksum.json"
},
- {
- "type": "file",
- "url": "https://static.crates.io/crates/atk/atk-0.14.0.crate",
- "sha256": "a83b21d2aa75e464db56225e1bda2dd5993311ba1095acaa8fa03d1ae67026ba",
- "dest": "cargo/vendor",
- "dest-filename": "atk-0.14.0.crate"
- },
- {
- "type": "file",
- "url": "data:%7B%22package%22%3A%20%22a83b21d2aa75e464db56225e1bda2dd5993311ba1095acaa8fa03d1ae67026ba%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/atk-0.14.0",
- "dest-filename": ".cargo-checksum.json"
- },
- {
- "type": "file",
- "url": "https://static.crates.io/crates/atk-sys/atk-sys-0.14.0.crate",
- "sha256": "badcf670157c84bb8b1cf6b5f70b650fed78da2033c9eed84c4e49b11cbe83ea",
- "dest": "cargo/vendor",
- "dest-filename": "atk-sys-0.14.0.crate"
- },
- {
- "type": "file",
- "url": "data:%7B%22package%22%3A%20%22badcf670157c84bb8b1cf6b5f70b650fed78da2033c9eed84c4e49b11cbe83ea%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/atk-sys-0.14.0",
- "dest-filename": ".cargo-checksum.json"
- },
{
"type": "file",
"url": "https://static.crates.io/crates/atomic-waker/atomic-waker-1.0.0.crate",
@@ -1130,19 +1104,6 @@
"dest": "cargo/vendor/futures-util-0.3.16",
"dest-filename": ".cargo-checksum.json"
},
- {
- "type": "file",
- "url": "https://static.crates.io/crates/gdk/gdk-0.14.0.crate",
- "sha256": "679e22651cd15888e7acd01767950edca2ee9fcd6421fbf5b3c3b420d4e88bb0",
- "dest": "cargo/vendor",
- "dest-filename": "gdk-0.14.0.crate"
- },
- {
- "type": "file",
- "url": "data:%7B%22package%22%3A%20%22679e22651cd15888e7acd01767950edca2ee9fcd6421fbf5b3c3b420d4e88bb0%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/gdk-0.14.0",
- "dest-filename": ".cargo-checksum.json"
- },
{
"type": "file",
"url": "https://static.crates.io/crates/gdk-pixbuf/gdk-pixbuf-0.14.0.crate",
@@ -1171,15 +1132,28 @@
},
{
"type": "file",
- "url": "https://static.crates.io/crates/gdk-sys/gdk-sys-0.14.0.crate",
- "sha256": "0e091b3d3d6696949ac3b3fb3c62090e5bfd7bd6850bef5c3c5ea701de1b1f1e",
+ "url": "https://static.crates.io/crates/gdk4/gdk4-0.2.0.crate",
+ "sha256": "8ce41092cc569129a0afa34926e6dd1cf8411e25652d87febdea36859f7ff7ba",
"dest": "cargo/vendor",
- "dest-filename": "gdk-sys-0.14.0.crate"
+ "dest-filename": "gdk4-0.2.0.crate"
},
{
"type": "file",
- "url": "data:%7B%22package%22%3A%20%220e091b3d3d6696949ac3b3fb3c62090e5bfd7bd6850bef5c3c5ea701de1b1f1e%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/gdk-sys-0.14.0",
+ "url": "data:%7B%22package%22%3A%20%228ce41092cc569129a0afa34926e6dd1cf8411e25652d87febdea36859f7ff7ba%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/gdk4-0.2.0",
+ "dest-filename": ".cargo-checksum.json"
+ },
+ {
+ "type": "file",
+ "url": "https://static.crates.io/crates/gdk4-sys/gdk4-sys-0.2.0.crate",
+ "sha256": "ce39c71861b5bcde319fd4711a74e1bd6f4f474911170d51096597fef0b56011",
+ "dest": "cargo/vendor",
+ "dest-filename": "gdk4-sys-0.2.0.crate"
+ },
+ {
+ "type": "file",
+ "url": "data:%7B%22package%22%3A%20%22ce39c71861b5bcde319fd4711a74e1bd6f4f474911170d51096597fef0b56011%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/gdk4-sys-0.2.0",
"dest-filename": ".cargo-checksum.json"
},
{
@@ -1286,32 +1260,6 @@
"dest": "cargo/vendor/gio-sys-0.14.0",
"dest-filename": ".cargo-checksum.json"
},
- {
- "type": "file",
- "url": "https://static.crates.io/crates/gladis/gladis-1.0.0.crate",
- "sha256": "6ed050efc446f5350bbd26a3ffaf5ba1569f754a5ee5f0cb4b772e759a183327",
- "dest": "cargo/vendor",
- "dest-filename": "gladis-1.0.0.crate"
- },
- {
- "type": "file",
- "url": "data:%7B%22package%22%3A%20%226ed050efc446f5350bbd26a3ffaf5ba1569f754a5ee5f0cb4b772e759a183327%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/gladis-1.0.0",
- "dest-filename": ".cargo-checksum.json"
- },
- {
- "type": "file",
- "url": "https://static.crates.io/crates/gladis_proc_macro/gladis_proc_macro-1.0.0.crate",
- "sha256": "53987c930e0b3a52e33148d4f5f3cddc8019573139162155b59f85aa84abd264",
- "dest": "cargo/vendor",
- "dest-filename": "gladis_proc_macro-1.0.0.crate"
- },
- {
- "type": "file",
- "url": "data:%7B%22package%22%3A%20%2253987c930e0b3a52e33148d4f5f3cddc8019573139162155b59f85aa84abd264%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/gladis_proc_macro-1.0.0",
- "dest-filename": ".cargo-checksum.json"
- },
{
"type": "file",
"url": "https://static.crates.io/crates/glib/glib-0.14.2.crate",
@@ -1392,41 +1340,93 @@
},
{
"type": "file",
- "url": "https://static.crates.io/crates/gtk/gtk-0.14.0.crate",
- "sha256": "10ae864e5eab8bc8b6b8544ed259eb02dd61b25323b20e777a77aa289c05fd0c",
+ "url": "https://static.crates.io/crates/graphene-rs/graphene-rs-0.14.0.crate",
+ "sha256": "f1460a39f06e491e6112f27e71e51435c833ba370723224dd1743dfd1f201f19",
+ "dest": "cargo/vendor",
+ "dest-filename": "graphene-rs-0.14.0.crate"
+ },
+ {
+ "type": "file",
+ "url": "data:%7B%22package%22%3A%20%22f1460a39f06e491e6112f27e71e51435c833ba370723224dd1743dfd1f201f19%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/graphene-rs-0.14.0",
+ "dest-filename": ".cargo-checksum.json"
+ },
+ {
+ "type": "file",
+ "url": "https://static.crates.io/crates/graphene-sys/graphene-sys-0.14.0.crate",
+ "sha256": "e7d23fb7a9547e5f072a7e0cd49cd648fedeb786d122b106217511980cbb8962",
"dest": "cargo/vendor",
- "dest-filename": "gtk-0.14.0.crate"
+ "dest-filename": "graphene-sys-0.14.0.crate"
},
{
"type": "file",
- "url": "data:%7B%22package%22%3A%20%2210ae864e5eab8bc8b6b8544ed259eb02dd61b25323b20e777a77aa289c05fd0c%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/gtk-0.14.0",
+ "url": "data:%7B%22package%22%3A%20%22e7d23fb7a9547e5f072a7e0cd49cd648fedeb786d122b106217511980cbb8962%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/graphene-sys-0.14.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "file",
- "url": "https://static.crates.io/crates/gtk-sys/gtk-sys-0.14.0.crate",
- "sha256": "8c14c8d3da0545785a7c5a120345b3abb534010fb8ae0f2ef3f47c027fba303e",
+ "url": "https://static.crates.io/crates/gsk4/gsk4-0.2.0.crate",
+ "sha256": "64932b730eaad3340378a03d633616eeed6d6705b59b81c9f579c88be8932475",
"dest": "cargo/vendor",
- "dest-filename": "gtk-sys-0.14.0.crate"
+ "dest-filename": "gsk4-0.2.0.crate"
},
{
"type": "file",
- "url": "data:%7B%22package%22%3A%20%228c14c8d3da0545785a7c5a120345b3abb534010fb8ae0f2ef3f47c027fba303e%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/gtk-sys-0.14.0",
+ "url": "data:%7B%22package%22%3A%20%2264932b730eaad3340378a03d633616eeed6d6705b59b81c9f579c88be8932475%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/gsk4-0.2.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "file",
- "url": "https://static.crates.io/crates/gtk3-macros/gtk3-macros-0.14.0.crate",
- "sha256": "21de1da96dc117443fb03c2e270b2d34b7de98d0a79a19bbb689476173745b79",
+ "url": "https://static.crates.io/crates/gsk4-sys/gsk4-sys-0.2.0.crate",
+ "sha256": "685ffc776bedd91d68f47b41239525778b669432889721d7050d045270549b9a",
"dest": "cargo/vendor",
- "dest-filename": "gtk3-macros-0.14.0.crate"
+ "dest-filename": "gsk4-sys-0.2.0.crate"
},
{
"type": "file",
- "url": "data:%7B%22package%22%3A%20%2221de1da96dc117443fb03c2e270b2d34b7de98d0a79a19bbb689476173745b79%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/gtk3-macros-0.14.0",
+ "url": "data:%7B%22package%22%3A%20%22685ffc776bedd91d68f47b41239525778b669432889721d7050d045270549b9a%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/gsk4-sys-0.2.0",
+ "dest-filename": ".cargo-checksum.json"
+ },
+ {
+ "type": "file",
+ "url": "https://static.crates.io/crates/gtk4/gtk4-0.2.0.crate",
+ "sha256": "c49e0311dac847a8ebc05e31f5c44c596314ee3b16c5f638ccfe24086d24bf1b",
+ "dest": "cargo/vendor",
+ "dest-filename": "gtk4-0.2.0.crate"
+ },
+ {
+ "type": "file",
+ "url": "data:%7B%22package%22%3A%20%22c49e0311dac847a8ebc05e31f5c44c596314ee3b16c5f638ccfe24086d24bf1b%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/gtk4-0.2.0",
+ "dest-filename": ".cargo-checksum.json"
+ },
+ {
+ "type": "file",
+ "url": "https://static.crates.io/crates/gtk4-macros/gtk4-macros-0.2.0.crate",
+ "sha256": "bbe4b77996bcf1ef20208c00043edda854ca2091b4be5e6a7c367f0f3846fa67",
+ "dest": "cargo/vendor",
+ "dest-filename": "gtk4-macros-0.2.0.crate"
+ },
+ {
+ "type": "file",
+ "url": "data:%7B%22package%22%3A%20%22bbe4b77996bcf1ef20208c00043edda854ca2091b4be5e6a7c367f0f3846fa67%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/gtk4-macros-0.2.0",
+ "dest-filename": ".cargo-checksum.json"
+ },
+ {
+ "type": "file",
+ "url": "https://static.crates.io/crates/gtk4-sys/gtk4-sys-0.2.0.crate",
+ "sha256": "3737e91619cf4257d8a07834f7a2c035d4daeaf9ad8e3958e56b2c411dbdca18",
+ "dest": "cargo/vendor",
+ "dest-filename": "gtk4-sys-0.2.0.crate"
+ },
+ {
+ "type": "file",
+ "url": "data:%7B%22package%22%3A%20%223737e91619cf4257d8a07834f7a2c035d4daeaf9ad8e3958e56b2c411dbdca18%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/gtk4-sys-0.2.0",
"dest-filename": ".cargo-checksum.json"
},
{
@@ -1873,41 +1873,41 @@
},
{
"type": "file",
- "url": "https://static.crates.io/crates/libc/libc-0.2.94.crate",
- "sha256": "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e",
+ "url": "https://static.crates.io/crates/libadwaita/libadwaita-0.1.0-alpha-2.crate",
+ "sha256": "d117dc3147a6e5917a4652f638cfcb512edf5d29de2af6a8e30e0d5de4f7395b",
"dest": "cargo/vendor",
- "dest-filename": "libc-0.2.94.crate"
+ "dest-filename": "libadwaita-0.1.0-alpha-2.crate"
},
{
"type": "file",
- "url": "data:%7B%22package%22%3A%20%2218794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/libc-0.2.94",
+ "url": "data:%7B%22package%22%3A%20%22d117dc3147a6e5917a4652f638cfcb512edf5d29de2af6a8e30e0d5de4f7395b%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/libadwaita-0.1.0-alpha-2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "file",
- "url": "https://static.crates.io/crates/libhandy/libhandy-0.8.0.crate",
- "sha256": "5bcf9c79ec810a62f442ffd568d2de233983dc91c160abee4949b67a647024ed",
+ "url": "https://static.crates.io/crates/libadwaita-sys/libadwaita-sys-0.1.0-alpha-2.crate",
+ "sha256": "64f1631562bdc3061b757290f17ea23237d59354ff6907069f40e1366fdb1426",
"dest": "cargo/vendor",
- "dest-filename": "libhandy-0.8.0.crate"
+ "dest-filename": "libadwaita-sys-0.1.0-alpha-2.crate"
},
{
"type": "file",
- "url": "data:%7B%22package%22%3A%20%225bcf9c79ec810a62f442ffd568d2de233983dc91c160abee4949b67a647024ed%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/libhandy-0.8.0",
+ "url": "data:%7B%22package%22%3A%20%2264f1631562bdc3061b757290f17ea23237d59354ff6907069f40e1366fdb1426%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/libadwaita-sys-0.1.0-alpha-2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "file",
- "url": "https://static.crates.io/crates/libhandy-sys/libhandy-sys-0.8.0.crate",
- "sha256": "1938b93a8f29417992c452b7f43e7eff8a9f8d25b7f0bc923ae9d75b50a9cde3",
+ "url": "https://static.crates.io/crates/libc/libc-0.2.94.crate",
+ "sha256": "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e",
"dest": "cargo/vendor",
- "dest-filename": "libhandy-sys-0.8.0.crate"
+ "dest-filename": "libc-0.2.94.crate"
},
{
"type": "file",
- "url": "data:%7B%22package%22%3A%20%221938b93a8f29417992c452b7f43e7eff8a9f8d25b7f0bc923ae9d75b50a9cde3%22%2C%20%22files%22%3A%20%7B%7D%7D",
- "dest": "cargo/vendor/libhandy-sys-0.8.0",
+ "url": "data:%7B%22package%22%3A%20%2218794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e%22%2C%20%22files%22%3A%20%7B%7D%7D",
+ "dest": "cargo/vendor/libc-0.2.94",
"dest-filename": ".cargo-checksum.json"
},
{
diff --git a/data/appstream/1.png b/data/appstream/1.png
index d3170cd4..5ca810e9 100644
Binary files a/data/appstream/1.png and b/data/appstream/1.png differ
diff --git a/data/appstream/2.png b/data/appstream/2.png
index 146e867c..fa4d58d1 100644
Binary files a/data/appstream/2.png and b/data/appstream/2.png differ
diff --git a/data/appstream/3.png b/data/appstream/3.png
index 5d4c32f0..1f9e8384 100644
Binary files a/data/appstream/3.png and b/data/appstream/3.png differ
diff --git a/data/dev.alextren.Spot.gschema.xml b/data/dev.alextren.Spot.gschema.xml
index 3051e3c8..12b74fce 100644
--- a/data/dev.alextren.Spot.gschema.xml
+++ b/data/dev.alextren.Spot.gschema.xml
@@ -10,10 +10,6 @@
-
- false
- Has the old cache been cleared?
-
true
Prefer dark theme
diff --git a/dev.alextren.Spot.develoment.json b/dev.alextren.Spot.develoment.json
index df430901..35003b42 100644
--- a/dev.alextren.Spot.develoment.json
+++ b/dev.alextren.Spot.develoment.json
@@ -36,14 +36,52 @@
"*.a"
],
"modules": [
+ {
+ "name": "libadwaita",
+ "buildsystem": "meson",
+ "config-opts": [
+ "-Dexamples=false",
+ "-Dtests=false"
+ ],
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://gitlab.gnome.org/GNOME/libadwaita.git",
+ "branch": "main"
+ }
+ ],
+ "modules": [
+ {
+ "name": "libsass",
+ "buildsystem": "meson",
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://github.com/lazka/libsass.git",
+ "branch": "meson"
+ }
+ ]
+ },
+ {
+ "name": "sassc",
+ "buildsystem": "meson",
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://github.com/lazka/sassc.git",
+ "branch": "meson"
+ }
+ ]
+ }
+ ]
+ },
{
"name": "spot",
"builddir": true,
"buildsystem": "meson",
"config-opts": [
"-Doffline=false",
- "-Dbuildtype=debug",
- "-Dfeatures="
+ "-Dbuildtype=debug"
],
"sources": [
{
diff --git a/dev.alextren.Spot.snapshots.json b/dev.alextren.Spot.snapshots.json
index c00c734c..86d95740 100644
--- a/dev.alextren.Spot.snapshots.json
+++ b/dev.alextren.Spot.snapshots.json
@@ -39,14 +39,52 @@
"*.a"
],
"modules": [
+ {
+ "name": "libadwaita",
+ "buildsystem": "meson",
+ "config-opts": [
+ "-Dexamples=false",
+ "-Dtests=false"
+ ],
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://gitlab.gnome.org/GNOME/libadwaita.git",
+ "branch": "main"
+ }
+ ],
+ "modules": [
+ {
+ "name": "libsass",
+ "buildsystem": "meson",
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://github.com/lazka/libsass.git",
+ "branch": "meson"
+ }
+ ]
+ },
+ {
+ "name": "sassc",
+ "buildsystem": "meson",
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://github.com/lazka/sassc.git",
+ "branch": "meson"
+ }
+ ]
+ }
+ ]
+ },
{
"name": "spot",
"builddir": true,
"buildsystem": "meson",
"config-opts": [
"-Doffline=true",
- "-Dbuildtype=debug",
- "-Dfeatures="
+ "-Dbuildtype=debug"
],
"sources": [
{
diff --git a/meson_options.txt b/meson_options.txt
index 84a1bdc9..a2d65a62 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,2 +1,2 @@
option('offline', type: 'boolean', value: true)
-option('features', type: 'string', value: 'warn-cache')
\ No newline at end of file
+option('features', type: 'string', value: '')
\ No newline at end of file
diff --git a/po/POTFILES b/po/POTFILES
index b14b1335..c0108ddf 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -1,97 +1,31 @@
-src/settings.rs
-src/api/cache.rs
-src/api/api_models.rs
-src/api/client.rs
-src/api/cached_client.rs
-src/api/mod.rs
-src/app/dispatch.rs
-src/app/components/details/details.rs
-src/app/components/details/details_model.rs
-src/app/components/details/mod.rs
-src/app/components/artist/mod.rs
-src/app/components/utils.rs
-src/app/components/playback/playback_info.rs
-src/app/components/playback/playback_control.rs
-src/app/components/playback/mod.rs
-src/app/components/playlist_details/playlist_details.rs
-src/app/components/playlist_details/playlist_details_model.rs
-src/app/components/playlist_details/mod.rs
-src/app/components/album/album.rs
-src/app/components/album/mod.rs
-src/app/components/artist_details/artist_details.rs
-src/app/components/artist_details/artist_details_model.rs
-src/app/components/artist_details/mod.rs
-src/app/components/login/login.rs
-src/app/components/login/mod.rs
+# grep gettext src/**/*.rs | cut -d: -f1 | uniq
+src/app/components/details/release_details.rs
+src/app/components/labels.rs
src/app/components/login/login_model.rs
-src/app/components/window/mod.rs
-src/app/components/saved_playlists/saved_playlists_model.rs
-src/app/components/saved_playlists/mod.rs
-src/app/components/saved_playlists/saved_playlists.rs
-src/app/components/library/library_model.rs
-src/app/components/library/library.rs
-src/app/components/library/mod.rs
-src/app/components/player_notifier.rs
-src/app/components/now_playing/now_playing.rs
-src/app/components/now_playing/mod.rs
-src/app/components/now_playing/now_playing_model.rs
-src/app/components/user_menu/user_menu_model.rs
-src/app/components/user_menu/mod.rs
-src/app/components/user_menu/user_menu.rs
-src/app/components/playlist/song_actions.rs
-src/app/components/playlist/song.rs
-src/app/components/playlist/playlist.rs
-src/app/components/playlist/mod.rs
-src/app/components/navigation/navigation.rs
-src/app/components/navigation/factory.rs
+src/app/components/mod.rs
src/app/components/navigation/home.rs
-src/app/components/navigation/mod.rs
-src/app/components/navigation/navigation_model.rs
-src/app/components/notification/mod.rs
-src/app/components/search/search_bar_model.rs
-src/app/components/search/search_model.rs
-src/app/components/search/search_bar.rs
-src/app/components/search/mod.rs
+src/app/components/playback/playback_info.rs
src/app/components/search/search.rs
-src/app/components/labels.rs
-src/app/components/mod.rs
-src/app/components/selection/mod.rs
src/app/components/selection/selection_heading.rs
-src/app/components/selection/selection_tools.rs
-src/app/components/selection/selection_widgets.rs
-src/app/loader.rs
-src/app/list_store.rs
-src/app/credentials.rs
-src/app/models.rs
-src/app/state/app_model.rs
-src/app/state/selection_state.rs
-src/app/state/browser_state.rs
+src/app/components/user_menu/user_menu.rs
src/app/state/login_state.rs
-src/app/state/screen_states.rs
-src/app/state/playback_state.rs
-src/app/state/app_state.rs
-src/app/state/mod.rs
-src/app/gtypes/album_model.rs
-src/app/gtypes/song_model.rs
-src/app/gtypes/artist_model.rs
-src/app/gtypes/mod.rs
-src/app/mod.rs
-src/config.rs
-src/dbus/mpris.rs
-src/dbus/mod.rs
-src/dbus/types.rs
-src/player/player.rs
-src/player/mod.rs
src/main.rs
-src/app/components/details/details.ui
-src/app/components/artist/artist.ui
-src/app/components/playlist_details/playlist_details.ui
+
+# find src -name "*.ui*" -print
src/app/components/album/album.ui
+src/app/components/artist/artist.ui
src/app/components/artist_details/artist_details.ui
-src/app/components/login/login.ui
-src/app/components/saved_playlists/saved_playlists.ui
+src/app/components/details/details.ui
+src/app/components/details/release_details.ui
src/app/components/library/library.ui
+src/app/components/login/login.ui
src/app/components/now_playing/now_playing.ui
+src/app/components/playback/playback_controls.ui
+src/app/components/playback/playback_info.ui
+src/app/components/playback/playback_widget.ui
+src/app/components/playlist_details/playlist_details.ui
src/app/components/playlist/song.ui
+src/app/components/saved_playlists/saved_playlists.ui
src/app/components/search/search.ui
-src/window.ui.in
\ No newline at end of file
+src/app/components/user_details/user_details.ui
+src/window.ui.in
diff --git a/po/ca.po b/po/ca.po
index 97314e53..d5168b55 100644
--- a/po/ca.po
+++ b/po/ca.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-03-17 22:33+0100\n"
"Last-Translator: Ícar N. S. \n"
"Language-Team: \n"
@@ -18,83 +18,37 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.4.1\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "Quant a"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Llançaments"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "Millors pistes"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Cap cançó en reproducció"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
msgstr ""
-"No s'ha pogut desar la sessió. Assegura't que l'anell de claus de la sessió "
-"està desbloquejat."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Surt"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Tanca la sessió"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Biblioteca"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Llistes de reproducció"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "S'està reproduïnt"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Cerca resultats per"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -126,13 +80,47 @@ msgstr "Suprimeix de la cua"
msgid "Add to {}"
msgstr ""
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+"No s'ha pogut desar la sessió. Assegura't que l'anell de claus de la sessió "
+"està desbloquejat."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr "Ha ocorregut un error. Comprova els registres per més detalls!"
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Biblioteca"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Llistes de reproducció"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "S'està reproduïnt"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Cap cançó en reproducció"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Cerca resultats per"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Cap cançó seleccionada"
@@ -143,52 +131,67 @@ msgid_plural "songs selected"
msgstr[0] "cançó seleccionada"
msgstr[1] "cançons seleccionades"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "Quant a"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Surt"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Tanca la sessió"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Connexió restablerta"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Millors pistes"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Llançaments"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Inicia la sessió a Spotify Premium"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Nom d'usuari"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Contrasenya"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "L'autenticació ha fallat!"
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Inicia sessió"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Àlbums"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Artistes"
diff --git a/po/cs.po b/po/cs.po
index eaea5cbd..1502f2da 100644
--- a/po/cs.po
+++ b/po/cs.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-03-17 17:03+0100\n"
"Last-Translator: Ondřej Sluka \n"
"Language-Team: none\n"
@@ -17,83 +17,37 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "O aplikaci"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Diskografie"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "Populární"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Nic nehraje"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
msgstr ""
-"Heslo nelze uložit. Zkontrolujte, že je klíčenka (session keyring) odemčená."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Konec"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Odhlásit se"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Knihovna"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Playlisty"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "Právě hraje"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Výsledky vyhledávání pro"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -125,13 +79,46 @@ msgstr "Odebrat z fronty"
msgid "Add to {}"
msgstr ""
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+"Heslo nelze uložit. Zkontrolujte, že je klíčenka (session keyring) odemčená."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr "Došlo k chybě. Podrobnosti najdete v protokolu."
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Knihovna"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Playlisty"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "Právě hraje"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Nic nehraje"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Výsledky vyhledávání pro"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Nebyla vybrána žádná skladba"
@@ -143,52 +130,67 @@ msgstr[0] "skladba vybrána"
msgstr[1] "skladby vybrány"
msgstr[2] "skladeb vybráno"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "O aplikaci"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Konec"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Odhlásit se"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Připojení obnoveno"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Populární"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Diskografie"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Přihlásit se ke Spotify Premium"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Uživatelské jméno"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Heslo"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "Chyba autentizace!"
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Přihlásit se"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Alba"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Umělci"
diff --git a/po/de.po b/po/de.po
index 2b46bbb7..551a1323 100644
--- a/po/de.po
+++ b/po/de.po
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-05-09 13:58+0200\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -23,83 +23,37 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.4.3\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "Info"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Diskografie"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "Beliebte Titel"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Es wird kein Titel gespielt"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
msgstr ""
-"Das Passwort konnte nicht gespeichert werden. Stellen Sie sicher, dass der "
-"Sitzungs-Schlüsselbund entsperrt ist."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Beenden"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Abmelden"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Bibliothek"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Playlists"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "Es läuft"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Suchergebnisse für"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -131,14 +85,48 @@ msgstr "Von Warteschlange entfernen"
msgid "Add to {}"
msgstr ""
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+"Das Passwort konnte nicht gespeichert werden. Stellen Sie sicher, dass der "
+"Sitzungs-Schlüsselbund entsperrt ist."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr ""
"Es ist ein Fehler aufgetreten. Überprüfen Sie die Protokolle für Details!"
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Bibliothek"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Playlists"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "Es läuft"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Es wird kein Titel gespielt"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Suchergebnisse für"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Kein Titel ausgewählt"
@@ -149,52 +137,67 @@ msgid_plural "songs selected"
msgstr[0] "Titel ausgewählt"
msgstr[1] "Titel ausgewählt"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "Info"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Beenden"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Abmelden"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Verbindung wiederhergestellt"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Beliebte Titel"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Diskografie"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Bei Spotify Premium anmelden"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Benutzername"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Passwort"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "Anmeldung fehlgeschlagen!"
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Anmelden"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Alben"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Künstler"
diff --git a/po/en.po b/po/en.po
index 57249a98..a6ce2c7e 100644
--- a/po/en.po
+++ b/po/en.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-03-16 19:21+0100\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -17,81 +17,37 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "About"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Releases"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "Top tracks"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "No song playing"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
-msgstr "Could not save password. Make sure the session keyring is unlocked."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Quit"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Log out"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Library"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Playlists"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "Now playing"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Search results for"
+msgstr ""
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -123,13 +79,45 @@ msgstr "Remove from queue"
msgid "Add to {}"
msgstr "Add to {}"
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr "Could not save password. Make sure the session keyring is unlocked."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr "An error occured. Check logs for details!"
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Library"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Playlists"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "Now playing"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "No song playing"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Search results for"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "No song selected"
@@ -140,52 +128,67 @@ msgid_plural "songs selected"
msgstr[0] "song selected"
msgstr[1] "songs selected"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "About"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Quit"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Log out"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Connection restored"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Top tracks"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Releases"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Login to Spotify Premium"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Username"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Password"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "Authentication failed!"
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Log in"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Albums"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Artists"
diff --git a/po/es.po b/po/es.po
index 26c35b12..ee2c0604 100644
--- a/po/es.po
+++ b/po/es.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-03-21 09:47+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -18,83 +18,37 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.4.1\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "Acerca de"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Lanzamientos"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "Mejores canciones"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Ninguna canción en reproducción"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
msgstr ""
-"No se pudo guardar la contraseña. Asegúrate de que el anillo de claves esta "
-"desbloqueado."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Salir"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Cerrar sesión"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Biblioteca"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Lista de reproducción"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "Reproduciendo ahora"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Buscar resultados para"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -126,13 +80,47 @@ msgstr "Borrar de la lista"
msgid "Add to {}"
msgstr ""
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+"No se pudo guardar la contraseña. Asegúrate de que el anillo de claves esta "
+"desbloqueado."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr "Ha ocurrido un error. Revisa los logs para más detalles!"
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Biblioteca"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Lista de reproducción"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "Reproduciendo ahora"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Ninguna canción en reproducción"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Buscar resultados para"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Ninguna canción seleccionada"
@@ -143,52 +131,67 @@ msgid_plural "songs selected"
msgstr[0] "canción seleccionada"
msgstr[1] "canciones seleccionadas"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "Acerca de"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Salir"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Cerrar sesión"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Conexión restaurada"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Mejores canciones"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Lanzamientos"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Iniciar sesión en Spotify Premium"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Usuario"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Contraseña"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "Autenticación fallida!"
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Iniciar sesión"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Álbumes"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Artistas"
diff --git a/po/fr.po b/po/fr.po
index f3359e84..92716ae0 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-03-15 19:18+0100\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -17,83 +17,37 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "À propos"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Discographie"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "Morceaux populaires"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Aucune lecture en cours"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
msgstr ""
-"Le mot de passe n'a pu être enregistré, assurez-vous que le Trousseau de "
-"session est déverouillé."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Quitter"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Déconnexion"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Bibliothèque"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Listes de lecture"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "En cours de lecture"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Résultats pour"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -125,15 +79,49 @@ msgstr "Retirer de la file d'attente"
msgid "Add to {}"
msgstr "Ajouter à {}"
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+"Le mot de passe n'a pu être enregistré, assurez-vous que le Trousseau de "
+"session est déverouillé."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr ""
"Une erreur est survenue. Consultez les journaux de débogage pour plus "
"d'information."
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Bibliothèque"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Listes de lecture"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "En cours de lecture"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Aucune lecture en cours"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Résultats pour"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Aucun morceau sélectionné"
@@ -144,52 +132,67 @@ msgid_plural "songs selected"
msgstr[0] "morceau sélectionné"
msgstr[1] "morceaux sélectionnés"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "À propos"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Quitter"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Déconnexion"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Connexion rétablie"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Morceaux populaires"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Discographie"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Connexion à Spotify Premium"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Nom d'utilisateur"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Mot de passe"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "L'authentification a échoué !"
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Connexion"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Albums"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Artistes"
diff --git a/po/id.po b/po/id.po
index 4fab115a..3ae8a25e 100644
--- a/po/id.po
+++ b/po/id.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-08-18 08:20+0700\n"
"Last-Translator: Kukuh Syafaat \n"
"Language-Team: \n"
@@ -18,79 +18,35 @@ msgstr ""
"Plural-Forms: nplurals=2; plural= n!=1;\n"
"X-Generator: Poedit 3.0\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "Tentang"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr "oleh"
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr "Label:"
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
msgid "Released:"
msgstr "Dirilis:"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
msgid "Tracks:"
msgstr "Trek:"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr "Durasi:"
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] "Hak Cipta:"
-msgstr[1] "Hak Cipta:"
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Tidak ada lagu yang diputar"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
-msgstr "Tak bisa menyimpan sandi. Pastikan bahwa kunci sesi tidak terkunci."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Keluar"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Log keluar"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Pustaka"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Daftar putar"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "Sedang memutar"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Hasil pencarian untuk"
+msgstr "Hak Cipta:"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -122,13 +78,45 @@ msgstr "Hapus dari antrian"
msgid "Add to {}"
msgstr "Tambah ke {}"
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr "Tak bisa menyimpan sandi. Pastikan bahwa kunci sesi tidak terkunci."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr "Timbul galat. Periksa log untuk detailnya!"
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Pustaka"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Daftar putar"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "Sedang memutar"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Tidak ada lagu yang diputar"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Hasil pencarian untuk"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Tidak ada lagu yang dipilih"
@@ -139,51 +127,66 @@ msgid_plural "songs selected"
msgstr[0] "lagu dipilih"
msgstr[1] "lagu dipilih"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "Tentang"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Keluar"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Log keluar"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Sambungan dipulihkan"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Trek teratas"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Rilis"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Masuk ke Spotify Premium"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Nama Pengguna"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Kata Sandi"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "Autentikasi gagal!"
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Log masuk"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Album"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Artis"
diff --git a/po/nl.po b/po/nl.po
index d712f3ed..647307cd 100644
--- a/po/nl.po
+++ b/po/nl.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-03-16 19:41+0100\n"
"Last-Translator: Heimen Stoffels \n"
"Language-Team: \n"
@@ -18,83 +18,37 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.4.2\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "Over"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Uitgaven"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "Topnummers"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Er wordt niks afgespeeld"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
msgstr ""
-"Het wachtwoord kan niet worden opgeslagen - zorg dat de sessiesleutelhanger "
-"ontgrendeld is."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Afsluiten"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Uitloggen"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Verzameling"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Afspeellijsten"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "Je luistert naar"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Zoekresultaten voor"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -126,13 +80,47 @@ msgstr "Verwijderen uit wachtrij"
msgid "Add to {}"
msgstr ""
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+"Het wachtwoord kan niet worden opgeslagen - zorg dat de sessiesleutelhanger "
+"ontgrendeld is."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr "Er is een fout opgetreden - bekijk het logboek!"
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Verzameling"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Afspeellijsten"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "Je luistert naar"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Er wordt niks afgespeeld"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Zoekresultaten voor"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Geen nummer geselecteerd"
@@ -143,52 +131,67 @@ msgid_plural "songs selected"
msgstr[0] "nummer geselecteerd"
msgstr[1] "nummers geselecteerd"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "Over"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Afsluiten"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Uitloggen"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "De verbinding is hersteld"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Topnummers"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Uitgaven"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Inloggen op Spotify Premium"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Gebruikersnaam"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Wachtwoord"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "Het inloggen is mislukt."
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Inloggen"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Albums"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Artiesten"
diff --git a/po/pl.po b/po/pl.po
index fc9c1cab..ae5ca262 100644
--- a/po/pl.po
+++ b/po/pl.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-03-19 14:31+0100\n"
"Last-Translator: Jonasz Potoniec \n"
"Language-Team: \n"
@@ -19,84 +19,37 @@ msgstr ""
"|| n%100>14) ? 1 : 2);\n"
"X-Generator: Poedit 2.4.2\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "O programie"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Premiery"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "Najlepsze piosenki"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Aktualnie nie gra żadna piosenka"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
msgstr ""
-"Nie można zapisać hasła. Upewnij się że narzędzie zarządzające kluczami jest "
-"odblokowane."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Wyjdź"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Wyloguj się"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Biblioteka"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Playlisty"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "Aktualnie odtwarzane"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Wyniki wyszukiwania dla"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -128,13 +81,47 @@ msgstr "Usuń z kolejki"
msgid "Add to {}"
msgstr ""
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+"Nie można zapisać hasła. Upewnij się że narzędzie zarządzające kluczami jest "
+"odblokowane."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr "Wystąpił błąd. Sprawdź logi po więcej informacji!"
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Biblioteka"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Playlisty"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "Aktualnie odtwarzane"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Aktualnie nie gra żadna piosenka"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Wyniki wyszukiwania dla"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Nie wybrano piosenki"
@@ -146,52 +133,67 @@ msgstr[0] "wybrana piosenka"
msgstr[1] "wybrane piosenki"
msgstr[2] "wybranych piosenek"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "O programie"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Wyjdź"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Wyloguj się"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Odzyskano połączenie"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Najlepsze piosenki"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Premiery"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Zaloguj się do Spotify Premium"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Nazwa użytkownika"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Hasło"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "Błąd autoryzacji!"
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Zaloguj"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Albumy"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Artyści"
diff --git a/po/pt.po b/po/pt.po
index 7edb1264..d9e7e31c 100644
--- a/po/pt.po
+++ b/po/pt.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-03-24 17:40+0000\n"
"Last-Translator: Hugo Gonçalves \n"
"Language-Team: none\n"
@@ -17,83 +17,37 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.3\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "Sobre"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Lançamentos"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "Melhores faixas"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Nenhuma canção a ser reproduzida"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
msgstr ""
-"Não foi possível guardar a palavra-passe. Garanta que o chaveiro da sessão "
-"está desbloqueado."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Sair"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Terminar sessão"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Biblioteca"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Listas de reprodução"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "A reproduzir"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Pesquisar resultados para"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -125,13 +79,47 @@ msgstr "Remover da fila"
msgid "Add to {}"
msgstr ""
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+"Não foi possível guardar a palavra-passe. Garanta que o chaveiro da sessão "
+"está desbloqueado."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr "Ocorreu um erro! Verifique os logs para mais detalhes!"
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Biblioteca"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Listas de reprodução"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "A reproduzir"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Nenhuma canção a ser reproduzida"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Pesquisar resultados para"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Nenhuma canção selecionada"
@@ -142,52 +130,67 @@ msgid_plural "songs selected"
msgstr[0] "canção selecionada"
msgstr[1] "canções selecionadas"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "Sobre"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Sair"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Terminar sessão"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Conexão restaurada"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Melhores faixas"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Lançamentos"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Autenticar-se no Spotify Premium"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Utilizador"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Palavra-passe"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "Autenticação falhou!"
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Autenticar"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Álbums"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Artistas"
diff --git a/po/pt_BR.po b/po/pt_BR.po
index 9237fbab..59bf6e28 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-06-28 15:11-0300\n"
"Last-Translator: Lucas Araujo \n"
"Language-Team: \n"
@@ -18,83 +18,37 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 2.3\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "Sobre"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Lançamentos"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "Melhores faixas"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Nenhum música sendo executada"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
msgstr ""
-"Não foi possível salvar a senha. Verifique se o seu chaveiro de sessão está "
-"desbloqueado."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Fechar"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Sair"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Biblioteca"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Listas de reprodução"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "Tocando agora"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Procurar por"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -126,13 +80,47 @@ msgstr "Remover da fila"
msgid "Add to {}"
msgstr "Adicionar a {}"
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+"Não foi possível salvar a senha. Verifique se o seu chaveiro de sessão está "
+"desbloqueado."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr "Ocorreu um erro. Verifique os logs para mais detalhes!"
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Biblioteca"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Listas de reprodução"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "Tocando agora"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Nenhum música sendo executada"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Procurar por"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Nenhuma música selecionada"
@@ -143,51 +131,66 @@ msgid_plural "songs selected"
msgstr[0] "música selecionada"
msgstr[1] "músicas selecionadas"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "Sobre"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Fechar"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Sair"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Conexão reestabelecida"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Melhores faixas"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Lançamentos"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Entrar no Spotify Premium"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Nome de usuário"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Senha"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "A autenticação falhou!"
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Entrar"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Álbuns"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Artistas"
diff --git a/po/ru.po b/po/ru.po
index 3bc55c62..223333a1 100644
--- a/po/ru.po
+++ b/po/ru.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-05-07 13:40+0300\n"
"Last-Translator: Automatically generated\n"
"Language-Team: Paul Bragin \n"
@@ -19,83 +19,37 @@ msgstr ""
"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
"X-Generator: Poedit 2.4.3\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "Об"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Релизы"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "Топ треков"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Песня сейчас не играет"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
msgstr ""
-"Не удалось сохранить пароль. Проверьте, GNOME Keyring/KDE wallet открыта."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Выйти из программы"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Выйти из аккаунта"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Библиотека"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Плейлисты"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "Сейчас играет"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Результаты поиска для"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -127,13 +81,46 @@ msgstr "Удалить из очереди"
msgid "Add to {}"
msgstr ""
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+"Не удалось сохранить пароль. Проверьте, GNOME Keyring/KDE wallet открыта."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr "Ошибка появилась. Посмотрите в логи за подробностями!"
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Библиотека"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Плейлисты"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "Сейчас играет"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Песня сейчас не играет"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Результаты поиска для"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Песня не выбрана"
@@ -145,52 +132,67 @@ msgstr[0] "песня выбрана"
msgstr[1] "песни выбраны"
msgstr[2] "песни были выбраны"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "Об"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Выйти из программы"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Выйти из аккаунта"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Подключение восстановлено"
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "Топ треков"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Релизы"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Войти в Spotify Premium"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Имя аккаунта"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Пароль"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "Вход не удался!"
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Войти"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Альбомы"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Артисты"
diff --git a/po/spot.pot b/po/spot.pot
index 7b92c578..6e4b946d 100644
--- a/po/spot.pot
+++ b/po/spot.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -18,78 +18,34 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr ""
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
msgid "Released:"
msgstr ""
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
msgid "Tracks:"
msgstr ""
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr ""
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
-msgstr ""
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr ""
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr ""
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr ""
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr ""
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr ""
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
msgstr ""
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
@@ -122,13 +78,45 @@ msgstr ""
msgid "Add to {}"
msgstr ""
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr ""
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr ""
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr ""
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr ""
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr ""
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr ""
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr ""
@@ -139,51 +127,66 @@ msgid_plural "songs selected"
msgstr[0] ""
msgstr[1] ""
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr ""
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr ""
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr ""
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr ""
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr ""
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr ""
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr ""
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr ""
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr ""
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr ""
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr ""
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr ""
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr ""
diff --git a/po/tr.po b/po/tr.po
index 32b906d9..c99737c5 100644
--- a/po/tr.po
+++ b/po/tr.po
@@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: spot\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-15 03:30-0400\n"
+"POT-Creation-Date: 2021-08-19 10:05+0200\n"
"PO-Revision-Date: 2021-06-14 00:00+0200\n"
"Last-Translator: Automatically generated\n"
"Language-Team: Yusuf Çınar Özmen \n"
@@ -15,83 +15,37 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#. translators: This is a menu entry.
-#: src/app/components/details/details.rs:147
-#: src/app/components/user_menu/user_menu.rs:60
-msgid "About"
-msgstr "Hakkında"
-
-#: src/app/components/details/details.rs:152
+#. translators: This is part of a larger label that reads " by "
+#: src/app/components/details/release_details.rs:101
msgid "by"
msgstr ""
-#: src/app/components/details/details.rs:158
+#. translators: This refers to a music label
+#: src/app/components/details/release_details.rs:108
msgid "Label:"
msgstr ""
-#: src/app/components/details/details.rs:162
+#. translators: This refers to a release date
+#: src/app/components/details/release_details.rs:115
#, fuzzy
msgid "Released:"
msgstr "Parçalar"
-#: src/app/components/details/details.rs:168
+#. translators: This refers to a number of tracks
+#: src/app/components/details/release_details.rs:122
#, fuzzy
msgid "Tracks:"
msgstr "En çok dinlenenler"
-#: src/app/components/details/details.rs:174
+#. translators: This refers to the duration of eg. an album
+#: src/app/components/details/release_details.rs:129
msgid "Duration:"
msgstr ""
-#: src/app/components/details/details.rs:180
+#. translators: Self explanatory
+#: src/app/components/details/release_details.rs:136
msgid "Copyright:"
-msgid_plural "Copyrights:"
-msgstr[0] ""
-msgstr[1] ""
-
-#. translators: Short text displayed instead of a song title when nothing plays
-#. Short text displayed instead of a song title when nothing plays
-#: src/app/components/playback/playback_info.rs:90 src/window.ui.in:488
-msgid "No song playing"
-msgstr "Herhangi bir şarkı oynatılmıyor"
-
-#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
-#: src/app/components/login/login_model.rs:52
-msgid "Could not save password. Make sure the session keyring is unlocked."
msgstr ""
-"Şifreniz kaydedilemedi. Oturum anahtarlığınızın kilidinin açık olduğundan "
-"emin olun."
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:62
-msgid "Quit"
-msgstr "Çıkış"
-
-#. translators: This is a menu entry.
-#: src/app/components/user_menu/user_menu.rs:67
-msgid "Log out"
-msgstr "Oturumdan çık"
-
-#. translators: This is a sidebar entry to browse to saved albums.
-#: src/app/components/navigation/home.rs:32
-msgid "Library"
-msgstr "Kütüphane"
-
-#. translators: This is a sidebar entry to browse to saved playlists.
-#: src/app/components/navigation/home.rs:37
-msgid "Playlists"
-msgstr "Oynatma listesi"
-
-#. This is the visible name for the play queue. It appears in the sidebar as well.
-#: src/app/components/navigation/home.rs:42
-#: src/app/components/now_playing/now_playing.ui:21
-msgid "Now playing"
-msgstr "Şu an oynatılıyor"
-
-#. translators: This text is part of a larger text that says "Search results for ".
-#: src/app/components/search/search.rs:123
-msgid "Search results for"
-msgstr "Arama sonuçları"
#. translators: This is part of a contextual menu attached to a single track; this entry allows viewing the album containing a specific track.
#: src/app/components/labels.rs:5
@@ -123,13 +77,47 @@ msgstr "Sıradan çıkar"
msgid "Add to {}"
msgstr "{} listesine ekle"
+#. translators: This notification shows up right after login if the password could not be stored in the keyring (that is, GNOME's keyring aka seahorse, or any other libsecret compliant secret store).
+#: src/app/components/login/login_model.rs:52
+msgid "Could not save password. Make sure the session keyring is unlocked."
+msgstr ""
+"Şifreniz kaydedilemedi. Oturum anahtarlığınızın kilidinin açık olduğundan "
+"emin olun."
+
#. translators: This notification is the default message for unhandled errors. Logs refer to console output.
-#: src/app/components/mod.rs:106
+#: src/app/components/mod.rs:105
msgid "An error occured. Check logs for details!"
msgstr "Bir hata meydana geldi. Günlükleri kontrol edin."
+#: src/app/components/navigation/home.rs:35
+msgid "Library"
+msgstr "Kütüphane"
+
+#. translators: This is a sidebar entry to browse to saved playlists.
+#: src/app/components/navigation/home.rs:41
+msgid "Playlists"
+msgstr "Oynatma listesi"
+
+#. This is the visible name for the play queue. It appears in the sidebar as well.
+#: src/app/components/navigation/home.rs:46
+#: src/app/components/now_playing/now_playing.ui:14
+msgid "Now playing"
+msgstr "Şu an oynatılıyor"
+
+#. translators: Short text displayed instead of a song title when nothing plays
+#. Short text displayed instead of a song title when nothing plays
+#: src/app/components/playback/playback_info.rs:58
+#: src/app/components/playback/playback_info.ui:32
+msgid "No song playing"
+msgstr "Herhangi bir şarkı oynatılmıyor"
+
+#. translators: This text is part of a larger text that says "Search results for ".
+#: src/app/components/search/search.rs:102
+msgid "Search results for"
+msgstr "Arama sonuçları"
+
#. This text appears when entering selection mode. It should be as short as possible.
-#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:26
+#: src/app/components/selection/selection_heading.rs:73 src/window.ui.in:51
msgid "No song selected"
msgstr "Herhangi bir şarkı seçilmedi"
@@ -140,51 +128,66 @@ msgid_plural "songs selected"
msgstr[0] "seçildi"
msgstr[1] "seçildi"
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:56
+msgid "About"
+msgstr "Hakkında"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:58
+msgid "Quit"
+msgstr "Çıkış"
+
+#. translators: This is a menu entry.
+#: src/app/components/user_menu/user_menu.rs:63
+msgid "Log out"
+msgstr "Oturumdan çık"
+
#: src/app/state/login_state.rs:117
msgid "Connection restored"
msgstr "Bağlantı geri yüklendi."
#. Title of the section that shows 5 of the top tracks for an artist, as defined by Spotify.
-#: src/app/components/artist_details/artist_details.ui:57
+#: src/app/components/artist_details/artist_details.ui:42
msgid "Top tracks"
msgstr "En çok dinlenenler"
#. Title of the sections that contains all releases from an artist (both singles and albums).
-#: src/app/components/artist_details/artist_details.ui:112
+#: src/app/components/artist_details/artist_details.ui:73
msgid "Releases"
msgstr "Parçalar"
#. Login window title -- shouldn't be too long, but must mention Premium (a premium account is required).
-#: src/app/components/login/login.ui:69
+#: src/app/components/login/login.ui:45
msgid "Login to Spotify Premium"
msgstr "Spotify Premium'a giriş yap"
#. Placeholder for the username field
-#: src/app/components/login/login.ui:97
+#: src/app/components/login/login.ui:64
msgid "Username"
msgstr "Kullanıcı adı"
#. Placeholder for the password field
-#: src/app/components/login/login.ui:112
+#: src/app/components/login/login.ui:72
msgid "Password"
msgstr "Şifre"
#. This error is shown when authentication fails.
-#: src/app/components/login/login.ui:156
+#: src/app/components/login/login.ui:95
msgid "Authentication failed!"
msgstr "Kimlik doğrulama başarısız."
#. Log in button label
-#: src/app/components/login/login.ui:181
+#: src/app/components/login/login.ui:110
msgid "Log in"
msgstr "Giriş yap"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:95
+#: src/app/components/search/search.ui:68
msgid "Albums"
msgstr "Albümler"
#. This is the title of a section of the search results
-#: src/app/components/search/search.ui:144
+#: src/app/components/search/search.ui:101
msgid "Artists"
msgstr "Sanatçılar"
diff --git a/src/api/mod.rs b/src/api/mod.rs
index cfc79c5f..e089f611 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -7,17 +7,6 @@ pub mod cache;
pub use cached_client::{CachedSpotifyClient, SpotifyApiClient, SpotifyResult};
pub use client::SpotifyApiError;
-pub async fn _clear_old_cache() -> Option<()> {
- let cache = cache::CacheManager::new(&[]).unwrap();
- let img_cache = regex::Regex::new(r"^[0-9]{15,30}\.jpg$").unwrap();
- cache
- .clear_cache_pattern("net", &*cached_client::ALL_CACHE)
- .await
- .ok()?;
- cache.clear_cache_pattern("img", &img_cache).await.ok()?;
- Some(())
-}
-
pub async fn clear_user_cache() -> Option<()> {
cache::CacheManager::new(&[])?
.clear_cache_pattern("spot/net", &*cached_client::USER_CACHE)
diff --git a/src/app.css b/src/app.css
index 1ed54344..2519e7cf 100644
--- a/src/app.css
+++ b/src/app.css
@@ -7,24 +7,10 @@
opacity: 1;
}
-.seek-bar {
- padding: 0;
- padding-bottom: 2px;
- min-height: 0;
+headerbar.selection-mode {
+ background-color: @theme_selected_bg_color;
}
-.seek-bar trough, .seek-bar highlight {
- border-radius: 0;
- border-left: none;
- border-right: none;
- min-height: 0;
-}
-
-.seek-bar--active trough, .seek-bar--active highlight {
- min-height: 5px;
-}
-
-.seek-bar highlight {
- border-left: none;
- border-right: none;
+button.tool, menubutton.tool {
+ -gtk-icon-size: 25px;
}
\ No newline at end of file
diff --git a/src/app/components/album/album.css b/src/app/components/album/album.css
index ba657e88..261d10fe 100644
--- a/src/app/components/album/album.css
+++ b/src/app/components/album/album.css
@@ -1,4 +1,27 @@
-.album button.album__cover {
+.album {
+ opacity: 0;
+ transition: opacity 300ms ease;
+}
+
+.album--loaded {
+ opacity: 1;
+}
+
+/* large style */
+
+leaflet.unfolded .album .album__cover {
+ min-width: 200px;
+ min-height: 200px;
+}
+
+/* small style */
+
+leaflet.folded .album .album__cover {
+ min-width: 100px;
+ min-height: 100px;
+}
+
+.album .album__cover {
padding: 0;
border-radius: 0;
}
@@ -11,3 +34,12 @@
.album label.album__artist {
font-size: 14px;
}
+
+.album__cover image {
+ border-radius: 4px;
+ transition: all 100ms ease;
+}
+
+.album__cover:hover image {
+ filter: brightness(1.15);
+}
\ No newline at end of file
diff --git a/src/app/components/album/album.rs b/src/app/components/album/album.rs
index e875c07e..b8925c8f 100644
--- a/src/app/components/album/album.rs
+++ b/src/app/components/album/album.rs
@@ -1,78 +1,113 @@
-use crate::app::components::{screen_add_css_provider, Component};
+use crate::app::components::screen_add_css_provider;
use crate::app::dispatch::Worker;
use crate::app::loader::ImageLoader;
use crate::app::models::AlbumModel;
-use gladis::Gladis;
+
use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::CompositeTemplate;
+
+mod imp {
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/album.ui")]
+ pub struct AlbumWidget {
+ #[template_child]
+ pub album_label: TemplateChild,
+
+ #[template_child]
+ pub artist_label: TemplateChild,
+
+ #[template_child]
+ pub cover_btn: TemplateChild,
+
+ #[template_child]
+ pub cover_image: TemplateChild,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for AlbumWidget {
+ const NAME: &'static str = "AlbumWidget";
+ type Type = super::AlbumWidget;
+ type ParentType = gtk::Box;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
-#[derive(Gladis, Clone)]
-struct AlbumWidget {
- root: gtk::Widget,
- revealer: gtk::Revealer,
- album_label: gtk::Label,
- artist_label: gtk::Label,
- cover_btn: gtk::Button,
- cover_image: gtk::Image,
+ impl ObjectImpl for AlbumWidget {}
+ impl WidgetImpl for AlbumWidget {}
+ impl BoxImpl for AlbumWidget {}
+}
+
+glib::wrapper! {
+ pub struct AlbumWidget(ObjectSubclass) @extends gtk::Widget, gtk::Box;
}
impl AlbumWidget {
pub fn new() -> Self {
screen_add_css_provider(resource!("/components/album.css"));
- Self::from_resource(resource!("/components/album.ui")).unwrap()
+ glib::Object::new(&[]).expect("Failed to create an instance of AlbumWidget")
}
-}
-pub struct Album {
- widget: AlbumWidget,
- model: AlbumModel,
-}
+ pub fn for_model(album_model: &AlbumModel, worker: Worker) -> Self {
+ let _self = Self::new();
+ _self.bind(album_model, worker);
+ _self
+ }
+
+ fn set_loaded(&self) {
+ let context = self.style_context();
+ context.add_class("album--loaded");
+ }
-impl Album {
- pub fn new(album_model: &AlbumModel, worker: Worker) -> Self {
- let widget = AlbumWidget::new();
+ fn set_image(&self, pixbuf: Option<&gdk_pixbuf::Pixbuf>) {
+ imp::AlbumWidget::from_instance(self)
+ .cover_image
+ .set_from_pixbuf(pixbuf);
+ }
+
+ fn bind(&self, album_model: &AlbumModel, worker: Worker) {
+ let widget = imp::AlbumWidget::from_instance(self);
+ widget.cover_image.set_overflow(gtk::Overflow::Hidden);
- let image = widget.cover_image.downgrade();
- let revealer = widget.revealer.downgrade();
if let Some(url) = album_model.cover_url() {
+ let _self = self.downgrade();
worker.send_local_task(async move {
- if let (Some(image), Some(revealer)) = (image.upgrade(), revealer.upgrade()) {
+ if let Some(_self) = _self.upgrade() {
let loader = ImageLoader::new();
let result = loader.load_remote(&url, "jpg", 200, 200).await;
- image.set_from_pixbuf(result.as_ref());
- revealer.set_reveal_child(true);
+ _self.set_image(result.as_ref());
+ _self.set_loaded();
}
});
} else {
- widget.revealer.set_reveal_child(true);
+ self.set_loaded();
}
album_model
- .bind_property("album", &widget.album_label, "label")
+ .bind_property("album", &*widget.album_label, "label")
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
.build();
album_model
- .bind_property("artist", &widget.artist_label, "label")
+ .bind_property("artist", &*widget.artist_label, "label")
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
.build();
-
- Self {
- widget,
- model: album_model.clone(),
- }
}
- pub fn connect_album_pressed(&self, f: F) {
- self.widget
+ pub fn connect_album_pressed(&self, f: F) {
+ imp::AlbumWidget::from_instance(self)
.cover_btn
- .connect_clicked(clone!(@weak self.model as model => move |_| {
- f(&model);
+ .connect_clicked(clone!(@weak self as _self => move |_| {
+ f(&_self);
}));
}
}
-
-impl Component for Album {
- fn get_root_widget(&self) -> >k::Widget {
- &self.widget.root
- }
-}
diff --git a/src/app/components/album/album.ui b/src/app/components/album/album.ui
index 674b7c2b..46e1fdf4 100644
--- a/src/app/components/album/album.ui
+++ b/src/app/components/album/album.ui
@@ -1,11 +1,8 @@
-
-
-
-
+
diff --git a/src/app/components/album/mod.rs b/src/app/components/album/mod.rs
index e853e857..fc74bceb 100644
--- a/src/app/components/album/mod.rs
+++ b/src/app/components/album/mod.rs
@@ -1,2 +1,2 @@
mod album;
-pub use album::Album;
+pub use album::AlbumWidget;
diff --git a/src/app/components/artist/artist.ui b/src/app/components/artist/artist.ui
index 23c05e63..e6de92f9 100644
--- a/src/app/components/artist/artist.ui
+++ b/src/app/components/artist/artist.ui
@@ -1,29 +1,23 @@
-
-
-
-
- True
- False
+
+
+
vertical
+ 1
150
150
- True
- True
- True
+ 1
center
center
- none
+ 0
-
- True
- False
+
center
center
- True
+ 1
150
@@ -31,23 +25,11 @@
-
- True
- True
- 0
-
- True
- False
Artist Name
-
- False
- True
- 2
-
-
+
diff --git a/src/app/components/artist/mod.rs b/src/app/components/artist/mod.rs
index dc68dcf9..5c460b7e 100644
--- a/src/app/components/artist/mod.rs
+++ b/src/app/components/artist/mod.rs
@@ -1,67 +1,89 @@
-use gladis::Gladis;
use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::CompositeTemplate;
-use crate::app::components::Component;
use crate::app::loader::ImageLoader;
use crate::app::models::ArtistModel;
use crate::app::Worker;
-#[derive(Gladis, Clone)]
-struct ArtistWidget {
- root: gtk::Widget,
- artist: gtk::Label,
- avatar_btn: gtk::Button,
- avatar: libhandy::Avatar,
+mod imp {
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/artist.ui")]
+ pub struct ArtistWidget {
+ #[template_child]
+ pub artist: TemplateChild,
+
+ #[template_child]
+ pub avatar_btn: TemplateChild,
+
+ #[template_child]
+ pub avatar: TemplateChild,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for ArtistWidget {
+ const NAME: &'static str = "ArtistWidget";
+ type Type = super::ArtistWidget;
+ type ParentType = gtk::Box;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for ArtistWidget {}
+ impl WidgetImpl for ArtistWidget {}
+ impl BoxImpl for ArtistWidget {}
+}
+
+glib::wrapper! {
+ pub struct ArtistWidget(ObjectSubclass) @extends gtk::Widget, gtk::Box;
}
impl ArtistWidget {
pub fn new() -> Self {
- Self::from_resource(resource!("/components/artist.ui")).unwrap()
+ glib::Object::new(&[]).expect("Failed to create an instance of ArtistWidget")
}
-}
-pub struct Artist {
- widget: ArtistWidget,
- model: ArtistModel,
-}
+ pub fn for_model(model: &ArtistModel, worker: Worker) -> Self {
+ let _self = Self::new();
+ _self.bind(model, worker);
+ _self
+ }
+
+ pub fn connect_artist_pressed(&self, f: F) {
+ imp::ArtistWidget::from_instance(self)
+ .avatar_btn
+ .connect_clicked(clone!(@weak self as _self => move |_| {
+ f(&_self);
+ }));
+ }
-impl Artist {
- pub fn new(artist_model: &ArtistModel, worker: Worker) -> Self {
- let widget = ArtistWidget::new();
+ fn bind(&self, model: &ArtistModel, worker: Worker) {
+ let widget = imp::ArtistWidget::from_instance(self);
- if let Some(url) = artist_model.image_url() {
+ if let Some(url) = model.image_url() {
let avatar = widget.avatar.downgrade();
worker.send_local_task(async move {
if let Some(avatar) = avatar.upgrade() {
let loader = ImageLoader::new();
let pixbuf = loader.load_remote(&url, "jpg", 200, 200).await;
- avatar.set_image_load_func(Some(Box::new(move |_| pixbuf.clone())));
+ let texture = pixbuf.as_ref().map(|p| gdk::Texture::for_pixbuf(p));
+ avatar.set_custom_image(texture.as_ref());
}
});
}
- artist_model
- .bind_property("artist", &widget.artist, "label")
+ model
+ .bind_property("artist", &*widget.artist, "label")
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
.build();
-
- Self {
- widget,
- model: artist_model.clone(),
- }
- }
-
- pub fn connect_artist_pressed(&self, f: F) {
- self.widget
- .avatar_btn
- .connect_clicked(clone!(@weak self.model as model => move |_| {
- f(&model);
- }));
- }
-}
-
-impl Component for Artist {
- fn get_root_widget(&self) -> >k::Widget {
- &self.widget.root
}
}
diff --git a/src/app/components/artist_details/artist_details.css b/src/app/components/artist_details/artist_details.css
index 3a7e1eb5..161cce3b 100644
--- a/src/app/components/artist_details/artist_details.css
+++ b/src/app/components/artist_details/artist_details.css
@@ -1,10 +1,10 @@
-list.artist__top-tracks {
+listview.artist__top-tracks {
padding: 8px;
border-radius: 8px;
margin: 8px;
}
-list.artist__top-tracks row {
+listview.artist__top-tracks row {
border-radius: 4px;
}
diff --git a/src/app/components/artist_details/artist_details.rs b/src/app/components/artist_details/artist_details.rs
index 41c409d8..d11ff741 100644
--- a/src/app/components/artist_details/artist_details.rs
+++ b/src/app/components/artist_details/artist_details.rs
@@ -1,25 +1,116 @@
-use gladis::Gladis;
use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::CompositeTemplate;
use std::rc::Rc;
-use crate::app::components::{screen_add_css_provider, Album, Component, EventListener, Playlist};
-use crate::app::models::*;
+use crate::app::components::{
+ screen_add_css_provider, AlbumWidget, Component, EventListener, Playlist,
+};
+use crate::app::{models::*, ListStore};
use crate::app::{AppEvent, BrowserEvent, Worker};
use super::ArtistDetailsModel;
-#[derive(Clone, Gladis)]
-struct ArtistDetailsWidget {
- pub root: gtk::ScrolledWindow,
- pub artist_name: gtk::Label,
- pub top_tracks: gtk::ListBox,
- pub artist_releases: gtk::FlowBox,
+mod imp {
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/artist_details.ui")]
+ pub struct ArtistDetailsWidget {
+ #[template_child]
+ pub scrolled_window: TemplateChild,
+
+ #[template_child]
+ pub artist_name: TemplateChild,
+
+ #[template_child]
+ pub top_tracks: TemplateChild,
+
+ #[template_child]
+ pub artist_releases: TemplateChild,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for ArtistDetailsWidget {
+ const NAME: &'static str = "ArtistDetailsWidget";
+ type Type = super::ArtistDetailsWidget;
+ type ParentType = gtk::Box;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for ArtistDetailsWidget {}
+ impl WidgetImpl for ArtistDetailsWidget {}
+ impl BoxImpl for ArtistDetailsWidget {}
+}
+
+glib::wrapper! {
+ pub struct ArtistDetailsWidget(ObjectSubclass) @extends gtk::Widget, gtk::Box;
}
impl ArtistDetailsWidget {
fn new() -> Self {
screen_add_css_provider(resource!("/components/artist_details.css"));
- Self::from_resource(resource!("/components/artist_details.ui")).unwrap()
+ glib::Object::new(&[]).expect("Failed to create an instance of ArtistDetailsWidget")
+ }
+
+ fn widget(&self) -> &imp::ArtistDetailsWidget {
+ imp::ArtistDetailsWidget::from_instance(self)
+ }
+
+ fn top_tracks_widget(&self) -> >k::ListView {
+ self.widget().top_tracks.as_ref()
+ }
+
+ fn set_artist_name(&self, name: &str) {
+ let context = self.style_context();
+ context.add_class("artist__loaded");
+ self.widget().artist_name.set_text(name);
+ }
+
+ fn connect_bottom_edge(&self, f: F)
+ where
+ F: Fn() + 'static,
+ {
+ self.widget()
+ .scrolled_window
+ .connect_edge_reached(move |_, pos| {
+ if let gtk::PositionType::Bottom = pos {
+ f()
+ }
+ });
+ }
+
+ fn bind_artist_releases(
+ &self,
+ worker: Worker,
+ store: &ListStore,
+ on_album_pressed: F,
+ ) where
+ F: Fn(&String) + Clone + 'static,
+ {
+ self.widget()
+ .artist_releases
+ .bind_model(Some(store.unsafe_store()), move |item| {
+ let item = item.downcast_ref::().unwrap();
+ let child = gtk::FlowBoxChild::new();
+ let album = AlbumWidget::for_model(item, worker.clone());
+ let f = on_album_pressed.clone();
+ album.connect_album_pressed(clone!(@weak item => move |_| {
+ if let Some(id) = item.uri().as_ref() {
+ f(id);
+ }
+ }));
+ child.set_child(Some(&album));
+ child.upcast::()
+ });
}
}
@@ -35,35 +126,24 @@ impl ArtistDetails {
let widget = ArtistDetailsWidget::new();
- let weak_model = Rc::downgrade(&model);
- widget.root.connect_edge_reached(move |_, pos| {
- if let (gtk::PositionType::Bottom, Some(model)) = (pos, weak_model.upgrade()) {
- let _ = model.load_more();
- }
- });
+ widget.connect_bottom_edge(clone!(@weak model => move || {
+ model.load_more();
+ }));
if let Some(store) = model.get_list_store() {
- let model_clone = Rc::clone(&model);
-
- widget
- .artist_releases
- .bind_model(Some(store.unsafe_store()), move |item| {
- let item = item.downcast_ref::().unwrap();
- let child = gtk::FlowBoxChild::new();
- let album = Album::new(item, worker.clone());
- let weak = Rc::downgrade(&model_clone);
- album.connect_album_pressed(move |a| {
- if let (Some(id), Some(m)) = (a.uri().as_ref(), weak.upgrade()) {
- m.open_album(id);
- }
- });
- child.add(album.get_root_widget());
- child.show_all();
- child.upcast::()
- });
+ widget.bind_artist_releases(
+ worker,
+ &*store,
+ clone!(@weak model => move |id| {
+ model.open_album(id);
+ }),
+ );
}
- let playlist = Box::new(Playlist::new(widget.top_tracks.clone(), Rc::clone(&model)));
+ let playlist = Box::new(Playlist::new(
+ widget.top_tracks_widget().clone(),
+ Rc::clone(&model),
+ ));
Self {
model,
@@ -74,16 +154,14 @@ impl ArtistDetails {
fn update_details(&mut self) {
if let Some(name) = self.model.get_artist_name() {
- let context = self.widget.root.style_context();
- context.add_class("artist__loaded");
- self.widget.artist_name.set_text(&name);
+ self.widget.set_artist_name(&name);
}
}
}
impl Component for ArtistDetails {
fn get_root_widget(&self) -> >k::Widget {
- self.widget.root.upcast_ref()
+ self.widget.upcast_ref()
}
fn get_children(&mut self) -> Option<&mut Vec>> {
diff --git a/src/app/components/artist_details/artist_details.ui b/src/app/components/artist_details/artist_details.ui
index 678259bb..fa082a6a 100644
--- a/src/app/components/artist_details/artist_details.ui
+++ b/src/app/components/artist_details/artist_details.ui
@@ -1,20 +1,14 @@
-
-
-
-
- True
- True
- never
+
+
-
- True
- False
-
+
+ never
+ 1
+ 1
+
- True
- False
8
8
8
@@ -23,34 +17,23 @@
16
- True
- False
start
8
8
Artist
- True
+ 1
0
-
- False
- True
- 0
-
- True
- False
vertical
- True
- False
start
8
8
@@ -59,72 +42,43 @@
-
- False
- True
- 0
-
-
- True
- False
- none
+
-
- False
- True
- 1
-
-
- False
- True
- 1
-
- True
- True
8
8
- True
+ 1
100
- True
- False
- True
+ 1
1
none
- False
+ 0
- True
- False
Releases
-
- False
- True
- 3
-
-
+
-
+
diff --git a/src/app/components/artist_details/artist_details_model.rs b/src/app/components/artist_details/artist_details_model.rs
index 0401fca8..55383b70 100644
--- a/src/app/components/artist_details/artist_details_model.rs
+++ b/src/app/components/artist_details/artist_details_model.rs
@@ -139,7 +139,11 @@ impl PlaylistModel for ArtistDetailsModel {
menu.append(Some(&*labels::VIEW_ALBUM), Some("song.view_album"));
for artist in song.artists.iter().filter(|a| self.id != a.id) {
menu.append(
- Some(&format!("{} {}", *labels::MORE_FROM, artist.name)),
+ Some(&format!(
+ "{} {}",
+ *labels::MORE_FROM,
+ glib::markup_escape_text(&artist.name)
+ )),
Some(&format!("song.view_artist_{}", artist.id)),
);
}
diff --git a/src/app/components/details/details.css b/src/app/components/details/details.css
index 3300ae28..b2308c90 100644
--- a/src/app/components/details/details.css
+++ b/src/app/components/details/details.css
@@ -30,12 +30,12 @@ label.details__album-label {
opacity: 1;
}
-list.details__songs {
+listview.details__songs {
padding: 8px;
border-radius: 8px;
}
-list.details__songs row {
+listview.details__songs row {
border-radius: 4px;
}
diff --git a/src/app/components/details/details.rs b/src/app/components/details/details.rs
index 0c30c001..f05b8356 100644
--- a/src/app/components/details/details.rs
+++ b/src/app/components/details/details.rs
@@ -1,8 +1,9 @@
-use gettextrs::{gettext, ngettext};
-use gladis::Gladis;
use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::CompositeTemplate;
use std::rc::Rc;
+use super::release_details::ReleaseDetailsWindow;
use super::DetailsModel;
use crate::app::components::{screen_add_css_provider, Component, EventListener, Playlist};
@@ -10,43 +11,154 @@ use crate::app::dispatch::Worker;
use crate::app::loader::ImageLoader;
use crate::app::{AppEvent, BrowserEvent};
-#[derive(Gladis, Clone)]
-struct DetailsWidget {
- pub root: gtk::Widget,
- pub album_label: gtk::Label,
- pub album_tracks: gtk::ListBox,
- pub album_art: gtk::Image,
- pub like_button: gtk::Button,
- pub artist_button: gtk::LinkButton,
- pub artist_button_label: gtk::Label,
- pub album_info: gtk::Button,
- info_window: libhandy::Window,
- info_close: gtk::Button,
- info_art: gtk::Image,
- info_album_artist: gtk::Label,
- info_label: gtk::Label,
- info_release: gtk::Label,
- info_tracks: gtk::Label,
- info_duration: gtk::Label,
- info_copyright: gtk::Label,
+mod imp {
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/details.ui")]
+ pub struct AlbumDetailsWidget {
+ #[template_child]
+ pub scrolled_window: TemplateChild,
+
+ #[template_child]
+ pub header_revealer: TemplateChild,
+
+ #[template_child]
+ pub album_label: TemplateChild,
+
+ #[template_child]
+ pub album_tracks: TemplateChild,
+
+ #[template_child]
+ pub album_art: TemplateChild,
+
+ #[template_child]
+ pub like_button: TemplateChild,
+
+ #[template_child]
+ pub info_button: TemplateChild,
+
+ #[template_child]
+ pub artist_button: TemplateChild,
+
+ #[template_child]
+ pub artist_button_label: TemplateChild,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for AlbumDetailsWidget {
+ const NAME: &'static str = "AlbumDetailsWidget";
+ type Type = super::AlbumDetailsWidget;
+ type ParentType = gtk::Box;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for AlbumDetailsWidget {}
+ impl WidgetImpl for AlbumDetailsWidget {}
+ impl BoxImpl for AlbumDetailsWidget {}
}
-impl DetailsWidget {
+glib::wrapper! {
+ pub struct AlbumDetailsWidget(ObjectSubclass) @extends gtk::Widget, gtk::Box;
+}
+
+impl AlbumDetailsWidget {
fn new() -> Self {
screen_add_css_provider(resource!("/components/details.css"));
- Self::from_resource(resource!("/components/details.ui")).unwrap()
+ glib::Object::new(&[]).expect("Failed to create an instance of AlbumDetailsWidget")
+ }
+
+ fn widget(&self) -> &imp::AlbumDetailsWidget {
+ imp::AlbumDetailsWidget::from_instance(self)
+ }
+
+ fn set_header_visible(&self, visible: bool) -> bool {
+ let widget = self.widget();
+ let is_up_to_date = widget.header_revealer.reveals_child() == visible;
+ if !is_up_to_date {
+ widget.header_revealer.set_reveal_child(visible);
+ }
+ is_up_to_date
+ }
+
+ fn connect_header(&self) {
+ self.set_header_visible(true);
+
+ let scroll_controller =
+ gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::VERTICAL);
+ scroll_controller.connect_scroll(
+ clone!(@weak self as _self => @default-return gtk::Inhibit(false), move |_, _, dy| {
+ gtk::Inhibit(!_self.set_header_visible(dy < 0f64))
+ }),
+ );
+
+ let widget = self.widget();
+ widget.scrolled_window.add_controller(&scroll_controller);
+ }
+
+ fn connect_liked(&self, f: F)
+ where
+ F: Fn() + 'static,
+ {
+ self.widget().like_button.connect_clicked(move |_| f());
+ }
+
+ fn connect_info(&self, f: F)
+ where
+ F: Fn() + 'static,
+ {
+ self.widget().info_button.connect_clicked(move |_| f());
+ }
+
+ fn connect_artist_clicked(&self, f: F)
+ where
+ F: Fn() + 'static,
+ {
+ self.widget().artist_button.connect_activate_link(move |_| {
+ f();
+ glib::signal::Inhibit(true)
+ });
+ }
+
+ fn album_tracks_widget(&self) -> >k::ListView {
+ self.widget().album_tracks.as_ref()
+ }
+
+ fn set_liked(&self, is_liked: bool) {
+ self.widget()
+ .like_button
+ .set_label(if is_liked { "♥" } else { "♡" });
}
fn set_loaded(&self) {
- let context = self.root.style_context();
+ let context = self.style_context();
context.add_class("details--loaded");
}
+
+ pub fn set_artwork(&self, art: &gdk_pixbuf::Pixbuf) {
+ self.widget().album_art.set_from_pixbuf(Some(art));
+ }
+
+ fn set_album_and_artist(&self, album: &str, artist: &str) {
+ let widget = self.widget();
+ widget.album_label.set_label(album);
+ widget.artist_button_label.set_label(artist);
+ }
}
pub struct Details {
model: Rc,
worker: Worker,
- widget: DetailsWidget,
+ widget: AlbumDetailsWidget,
+ modal: ReleaseDetailsWindow,
children: Vec>,
}
@@ -55,140 +167,96 @@ impl Details {
if model.get_album_info().is_none() {
model.load_album_info();
}
- let widget = DetailsWidget::new();
- let playlist = Box::new(Playlist::new(widget.album_tracks.clone(), model.clone()));
-
- widget
- .like_button
- .connect_clicked(clone!(@weak model => move |_| {
- model.toggle_save_album();
- }));
- let info_window = widget.info_window.clone();
+ let widget = AlbumDetailsWidget::new();
+ let playlist = Box::new(Playlist::new(
+ widget.album_tracks_widget().clone(),
+ model.clone(),
+ ));
- info_window.connect_delete_event(|info_window, _| info_window.hide_on_delete());
+ let modal = ReleaseDetailsWindow::new();
- info_window.connect_key_press_event(|info_window, event| {
- if let gdk::keys::constants::Escape = event.keyval() {
- info_window.hide()
- }
- glib::signal::Inhibit(false)
- });
+ widget.connect_liked(clone!(@weak model => move || model.toggle_save_album()));
- widget
- .info_close
- .connect_clicked(clone!(@weak info_window => move |_| info_window.hide()));
+ widget.connect_header();
- widget
- .album_info
- .connect_clicked(clone!(@weak info_window => move |_| info_window.show()));
+ widget.connect_info(clone!(@weak modal, @weak widget => move || {
+ let modal = modal.upcast_ref::();
+ modal.set_modal(true);
+ modal.set_transient_for(
+ widget
+ .root()
+ .and_then(|r| r.downcast::().ok())
+ .as_ref(),
+ );
+ modal.show();
+ }));
Self {
model,
worker,
widget,
+ modal,
children: vec![playlist],
}
}
fn update_liked(&self) {
- if let Some(album) = self.model.get_album_description() {
- let is_liked = album.is_liked;
- self.widget
- .like_button
- .set_label(if is_liked { "♥" } else { "♡" });
+ if let Some(info) = self.model.get_album_info() {
+ let is_liked = info.description.is_liked;
+ self.widget.set_liked(is_liked);
}
}
fn update_details(&mut self) {
- if let Some(info) = self.model.get_album_description() {
- let album = &info.title[..];
- let artist = &info.artists_name();
-
- self.widget.album_label.set_label(album);
- self.widget.artist_button_label.set_label(artist);
- let weak_model = Rc::downgrade(&self.model);
- self.widget.artist_button.connect_activate_link(move |_| {
- if let Some(model) = weak_model.upgrade() {
+ if let Some(album) = self.model.get_album_info() {
+ let details = &album.release_details;
+ let album = &album.description;
+
+ self.widget.set_liked(album.is_liked);
+ self.widget
+ .set_album_and_artist(&album.title[..], &album.artists_name());
+ self.widget
+ .connect_artist_clicked(clone!(@weak self.model as model => move || {
model.view_artist();
- }
- glib::signal::Inhibit(true)
- });
+ }));
- let widget = self.widget.clone();
- if let Some(art) = info.art.clone() {
- self.worker.send_local_task(async move {
- let pixbuf = ImageLoader::new()
- .load_remote(&art[..], "jpg", 100, 100)
- .await;
- widget.album_art.set_from_pixbuf(pixbuf.as_ref());
- widget.set_loaded();
- });
- } else {
- widget.set_loaded();
- }
- }
- }
+ self.modal.set_details(
+ &album.title,
+ &album.artists_name(),
+ &details.label,
+ &details.release_date,
+ album.songs.len(),
+ &album.formatted_time(),
+ &details.copyrights(),
+ );
- fn update_dialog(&mut self) {
- if let Some(album) = self.model.get_album_info() {
- let widget = self.widget.clone();
- let info = &album.release_details;
- let album = &album.description;
if let Some(art) = album.art.clone() {
+ let widget = self.widget.downgrade();
+ let modal = self.modal.downgrade();
+
self.worker.send_local_task(async move {
let pixbuf = ImageLoader::new()
.load_remote(&art[..], "jpg", 200, 200)
.await;
- widget.info_art.set_from_pixbuf(pixbuf.as_ref());
+ if let (Some(widget), Some(modal), Some(ref pixbuf)) =
+ (widget.upgrade(), modal.upgrade(), pixbuf)
+ {
+ widget.set_artwork(pixbuf);
+ widget.set_loaded();
+ modal.set_artwork(pixbuf);
+ }
});
+ } else {
+ self.widget.set_loaded();
}
-
- self.widget
- .info_window
- .set_title(&format!("{} {}", gettext("About"), album.title));
-
- self.widget.info_album_artist.set_text(&format!(
- "{} {} {}",
- album.title,
- gettext("by"),
- album.artists_name()
- ));
-
- self.widget
- .info_label
- .set_text(&format!("{} {}", gettext("Label:"), info.label));
-
- self.widget.info_release.set_text(&format!(
- "{} {}",
- gettext("Released:"),
- info.release_date
- ));
-
- self.widget.info_tracks.set_text(&format!(
- "{} {}",
- gettext("Tracks:"),
- album.songs.len()
- ));
-
- self.widget.info_duration.set_text(&format!(
- "{} {}",
- gettext("Duration:"),
- album.formatted_time()
- ));
-
- self.widget.info_copyright.set_text(&format!(
- "{} {}",
- ngettext("Copyright:", "Copyrights:", info.copyrights.len() as u32),
- info.copyrights()
- ));
}
}
}
impl Component for Details {
fn get_root_widget(&self) -> >k::Widget {
- &self.widget.root
+ self.widget.upcast_ref()
}
fn get_children(&mut self) -> Option<&mut Vec>> {
@@ -203,8 +271,6 @@ impl EventListener for Details {
if id == &self.model.id =>
{
self.update_details();
- self.update_liked();
- self.update_dialog();
}
AppEvent::BrowserEvent(BrowserEvent::AlbumSaved(id))
| AppEvent::BrowserEvent(BrowserEvent::AlbumUnsaved(id))
diff --git a/src/app/components/details/details.ui b/src/app/components/details/details.ui
index 11b5e439..798a4359 100644
--- a/src/app/components/details/details.ui
+++ b/src/app/components/details/details.ui
@@ -1,302 +1,59 @@
-
-
-
-
-
- False
- True
- 250
- dialog
+
+
+
-
- True
- False
+
+ 1
+ 1
+ 900
+ 8
+ 8
+ 8
+ 8
- True
- False
vertical
+ 1
+ 1
+ 8
-
- True
- False
- False
- True
- end
- 5
- none
-
-
- 22
- 22
- True
- False
- window-close-symbolic
-
-
-
-
-
- False
- False
- 0
-
-
-
-
- True
- True
- True
-
-
- True
- False
- none
-
-
- True
- False
- 900
- 360
-
-
- True
- False
- 10
- 10
- 5
- 10
- vertical
-
-
- 200
- 200
- True
- False
- center
- center
- emblem-music-symbolic
-
-
-
- False
- False
- 0
-
-
-
-
- True
- False
- 10
- vertical
- 1
-
-
- True
- False
- center
- center
- Album by Artist
- center
- True
- word-char
-
-
-
- False
- True
- 0
-
-
-
-
- True
- False
- 5
- Label
- center
- True
- word-char
-
-
- False
- True
- 1
-
-
-
-
- True
- False
- Release Date
- center
- True
- word-char
-
-
- False
- True
- 2
-
-
-
-
- True
- False
- Tracks
- center
- True
- word-char
-
-
- False
- True
- 3
-
-
-
-
- True
- False
- Duration
- center
- True
- word-char
-
-
- False
- True
- 4
-
-
-
-
- True
- False
- Copyright
- center
- True
- word-char
-
-
- False
- True
- 5
-
-
-
-
- False
- True
- 1
-
-
-
-
-
-
-
-
-
-
- True
- True
- 1
-
-
-
-
-
-
-
-
- True
- True
- never
- True
-
-
- True
- False
- etched-out
-
-
- True
- False
- 900
-
-
- True
- False
- start
- 16
- 16
- 16
- 16
- vertical
- 8
+
+
diff --git a/src/app/components/details/details_model.rs b/src/app/components/details/details_model.rs
index fb9251ba..ed66198c 100644
--- a/src/app/components/details/details_model.rs
+++ b/src/app/components/details/details_model.rs
@@ -193,7 +193,11 @@ impl PlaylistModel for DetailsModel {
let menu = gio::Menu::new();
for artist in song.artists.iter() {
menu.append(
- Some(&format!("{} {}", *labels::MORE_FROM, artist.name)),
+ Some(&format!(
+ "{} {}",
+ *labels::MORE_FROM,
+ glib::markup_escape_text(&artist.name)
+ )),
Some(&format!("song.view_artist_{}", artist.id)),
);
}
diff --git a/src/app/components/details/mod.rs b/src/app/components/details/mod.rs
index 32e87dc2..2d367f88 100644
--- a/src/app/components/details/mod.rs
+++ b/src/app/components/details/mod.rs
@@ -1,5 +1,7 @@
mod details;
mod details_model;
-pub use details::*;
-pub use details_model::*;
+pub use details::Details;
+pub use details_model::DetailsModel;
+
+mod release_details;
diff --git a/src/app/components/details/release_details.rs b/src/app/components/details/release_details.rs
new file mode 100644
index 00000000..a3d8db30
--- /dev/null
+++ b/src/app/components/details/release_details.rs
@@ -0,0 +1,140 @@
+use gettextrs::gettext;
+use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::CompositeTemplate;
+use libadwaita::subclass::prelude::*;
+
+mod imp {
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/release_details.ui")]
+ pub struct ReleaseDetailsWindow {
+ #[template_child]
+ pub close: TemplateChild,
+
+ #[template_child]
+ pub art: TemplateChild,
+
+ #[template_child]
+ pub album_artist: TemplateChild,
+
+ #[template_child]
+ pub label: TemplateChild,
+
+ #[template_child]
+ pub release: TemplateChild,
+
+ #[template_child]
+ pub tracks: TemplateChild,
+
+ #[template_child]
+ pub duration: TemplateChild,
+
+ #[template_child]
+ pub copyright: TemplateChild,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for ReleaseDetailsWindow {
+ const NAME: &'static str = "ReleaseDetailsWindow";
+ type Type = super::ReleaseDetailsWindow;
+ type ParentType = libadwaita::Window;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for ReleaseDetailsWindow {
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+ self.close
+ .connect_clicked(clone!(@weak obj => move |_| obj.hide()));
+ }
+ }
+
+ impl WidgetImpl for ReleaseDetailsWindow {}
+ impl AdwWindowImpl for ReleaseDetailsWindow {}
+ impl WindowImpl for ReleaseDetailsWindow {}
+}
+
+glib::wrapper! {
+ pub struct ReleaseDetailsWindow(ObjectSubclass) @extends gtk::Widget, libadwaita::Window;
+}
+
+impl ReleaseDetailsWindow {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create an instance of ReleaseDetailsWindow")
+ }
+
+ fn widget(&self) -> &imp::ReleaseDetailsWindow {
+ imp::ReleaseDetailsWindow::from_instance(self)
+ }
+
+ pub fn set_artwork(&self, art: &gdk_pixbuf::Pixbuf) {
+ self.widget().art.set_from_pixbuf(Some(art));
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ pub fn set_details(
+ &self,
+ album: &str,
+ artist: &str,
+ label: &str,
+ release_date: &str,
+ track_count: usize,
+ duration: &str,
+ copyright: &str,
+ ) {
+ let widget = self.widget();
+
+ widget.album_artist.set_text(&format!(
+ "{} {} {}",
+ album,
+ // translators: This is part of a larger label that reads " by "
+ gettext("by"),
+ artist
+ ));
+
+ widget.label.set_text(&format!(
+ "{} {}",
+ // translators: This refers to a music label
+ gettext("Label:"),
+ label
+ ));
+
+ widget.release.set_text(&format!(
+ "{} {}",
+ // translators: This refers to a release date
+ gettext("Released:"),
+ release_date
+ ));
+
+ widget.tracks.set_text(&format!(
+ "{} {}",
+ // translators: This refers to a number of tracks
+ gettext("Tracks:"),
+ track_count
+ ));
+
+ widget.duration.set_text(&format!(
+ "{} {}",
+ // translators: This refers to the duration of eg. an album
+ gettext("Duration:"),
+ duration
+ ));
+
+ widget.copyright.set_text(&format!(
+ "{} {}",
+ // translators: Self explanatory
+ gettext("Copyright:"),
+ copyright
+ ));
+ }
+}
diff --git a/src/app/components/details/release_details.ui b/src/app/components/details/release_details.ui
new file mode 100644
index 00000000..c12ebca0
--- /dev/null
+++ b/src/app/components/details/release_details.ui
@@ -0,0 +1,136 @@
+
+
+
+
+
+ 0
+ 1
+ 250
+
+
+
+
+ vertical
+
+
+ center
+ 1
+ end
+ 5
+ 5
+ 5
+ 5
+ 0
+
+
+ 22
+ 22
+ window-close-symbolic
+
+
+
+
+
+
+
+ 1
+ 1
+
+
+ 900
+ 360
+
+
+ 10
+ 10
+ 5
+ 10
+ vertical
+
+
+ 200
+ 200
+ 0
+ center
+ center
+ emblem-music-symbolic
+
+
+
+
+
+ 10
+ vertical
+ 1
+
+
+ center
+ center
+ Album by Artist
+ center
+ 1
+ word-char
+
+
+
+
+
+ 5
+ Label
+ center
+ 1
+ word-char
+
+
+
+
+ Release Date
+ center
+ 1
+ word-char
+
+
+
+
+ Tracks
+ center
+ 1
+ word-char
+
+
+
+
+ Duration
+ center
+ 1
+ word-char
+
+
+
+
+ Copyright
+ center
+ 1
+ word-char
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/library/library.rs b/src/app/components/library/library.rs
index 66746f87..109dd774 100644
--- a/src/app/components/library/library.rs
+++ b/src/app/components/library/library.rs
@@ -1,28 +1,91 @@
-use gladis::Gladis;
use gtk::prelude::*;
-
-use std::rc::{Rc, Weak};
+use gtk::subclass::prelude::*;
+use gtk::CompositeTemplate;
+use std::rc::Rc;
use super::LibraryModel;
-use crate::app::components::{Album, Component, EventListener};
+use crate::app::components::utils::wrap_flowbox_item;
+use crate::app::components::{AlbumWidget, Component, EventListener};
use crate::app::dispatch::Worker;
use crate::app::models::AlbumModel;
use crate::app::state::LoginEvent;
-use crate::app::AppEvent;
+use crate::app::{AppEvent, ListStore};
+
+mod imp {
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/library.ui")]
+ pub struct LibraryWidget {
+ #[template_child]
+ pub scrolled_window: TemplateChild,
+
+ #[template_child]
+ pub flowbox: TemplateChild,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for LibraryWidget {
+ const NAME: &'static str = "LibraryWidget";
+ type Type = super::LibraryWidget;
+ type ParentType = gtk::Box;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for LibraryWidget {}
+ impl WidgetImpl for LibraryWidget {}
+ impl BoxImpl for LibraryWidget {}
+}
-#[derive(Clone, Gladis)]
-struct LibraryWidget {
- pub scrolled_window: gtk::ScrolledWindow,
- pub flowbox: gtk::FlowBox,
+glib::wrapper! {
+ pub struct LibraryWidget(ObjectSubclass) @extends gtk::Widget, gtk::Box;
}
impl LibraryWidget {
- fn new() -> Self {
- Self::from_resource(resource!("/components/library.ui")).unwrap()
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create an instance of LibraryWidget")
}
- fn root(&self) -> >k::Widget {
- self.scrolled_window.upcast_ref()
+ fn connect_bottom_edge(&self, f: F)
+ where
+ F: Fn() + 'static,
+ {
+ imp::LibraryWidget::from_instance(self)
+ .scrolled_window
+ .connect_edge_reached(move |_, pos| {
+ if let gtk::PositionType::Bottom = pos {
+ f()
+ }
+ });
+ }
+
+ fn bind_albums(&self, worker: Worker, store: &ListStore, on_album_pressed: F)
+ where
+ F: Fn(&String) + Clone + 'static,
+ {
+ imp::LibraryWidget::from_instance(self).flowbox.bind_model(
+ Some(store.unsafe_store()),
+ move |item| {
+ wrap_flowbox_item(item, |album_model| {
+ let f = on_album_pressed.clone();
+ let album = AlbumWidget::for_model(album_model, worker.clone());
+ album.connect_album_pressed(clone!(@weak album_model => move |_| {
+ if let Some(id) = album_model.uri().as_ref() {
+ f(id);
+ }
+ }));
+ album
+ })
+ },
+ );
}
}
@@ -35,15 +98,10 @@ pub struct Library {
impl Library {
pub fn new(worker: Worker, model: LibraryModel) -> Self {
let model = Rc::new(model);
-
let widget = LibraryWidget::new();
-
- let weak_model = Rc::downgrade(&model);
- widget.scrolled_window.connect_edge_reached(move |_, pos| {
- if let (gtk::PositionType::Bottom, Some(model)) = (pos, weak_model.upgrade()) {
- let _ = model.load_more_albums();
- }
- });
+ widget.connect_bottom_edge(clone!(@weak model => move || {
+ model.load_more_albums();
+ }));
Self {
widget,
@@ -52,16 +110,14 @@ impl Library {
}
}
- fn bind_flowbox(&self, store: &gio::ListStore) {
- let weak_model = Rc::downgrade(&self.model);
- let worker_clone = self.worker.clone();
-
- self.widget.flowbox.bind_model(Some(store), move |item| {
- let item = item.downcast_ref::().unwrap();
- let child = create_album_for(item, worker_clone.clone(), weak_model.clone());
- child.show_all();
- child.upcast::()
- });
+ fn bind_flowbox(&self) {
+ self.widget.bind_albums(
+ self.worker.clone(),
+ &*self.model.get_list_store().unwrap(),
+ clone!(@weak self.model as model => move |id| {
+ model.open_album(id.clone());
+ }),
+ );
}
}
@@ -70,7 +126,7 @@ impl EventListener for Library {
match event {
AppEvent::Started => {
let _ = self.model.refresh_saved_albums();
- self.bind_flowbox(self.model.get_list_store().unwrap().unsafe_store())
+ self.bind_flowbox();
}
AppEvent::LoginEvent(LoginEvent::LoginCompleted(_)) => {
let _ = self.model.refresh_saved_albums();
@@ -82,25 +138,6 @@ impl EventListener for Library {
impl Component for Library {
fn get_root_widget(&self) -> >k::Widget {
- self.widget.root()
+ self.widget.as_ref()
}
}
-
-fn create_album_for(
- album_model: &AlbumModel,
- worker: Worker,
- model: Weak,
-) -> gtk::FlowBoxChild {
- let child = gtk::FlowBoxChild::new();
-
- let album = Album::new(album_model, worker);
- child.add(album.get_root_widget());
-
- album.connect_album_pressed(move |a| {
- if let (Some(model), Some(id)) = (model.upgrade(), a.uri()) {
- model.open_album(id);
- }
- });
-
- child
-}
diff --git a/src/app/components/library/library.ui b/src/app/components/library/library.ui
index aaa166e7..48f5f256 100644
--- a/src/app/components/library/library.ui
+++ b/src/app/components/library/library.ui
@@ -1,30 +1,25 @@
-
-
-
- True
- True
- always
- 250
+
+
-
- True
- False
-
+
+ 1
+ 1
+ always
+ 250
+
- True
- False
8
8
8
8
1
none
- False
+ 0
-
+
-
+
diff --git a/src/app/components/login/login.rs b/src/app/components/login/login.rs
index b8e50215..7e8f3892 100644
--- a/src/app/components/login/login.rs
+++ b/src/app/components/login/login.rs
@@ -1,6 +1,6 @@
-use gdk::{keys::constants::Return, EventKey};
-use gladis::Gladis;
use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::CompositeTemplate;
use std::rc::Rc;
use crate::app::components::EventListener;
@@ -9,128 +9,179 @@ use crate::app::state::{LoginCompletedEvent, LoginEvent};
use crate::app::AppEvent;
use super::LoginModel;
+mod imp {
-#[derive(Clone, Gladis)]
-struct LoginWidget {
- pub window: libhandy::Window,
- username: gtk::Entry,
- password: gtk::Entry,
- close_button: gtk::Button,
- login_button: gtk::Button,
- error_container: gtk::Revealer,
+ use libadwaita::subclass::prelude::AdwWindowImpl;
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/login.ui")]
+ pub struct LoginWindow {
+ #[template_child]
+ pub username: TemplateChild,
+
+ #[template_child]
+ pub password: TemplateChild,
+
+ #[template_child]
+ pub close_button: TemplateChild,
+
+ #[template_child]
+ pub login_button: TemplateChild,
+
+ #[template_child]
+ pub error_container: TemplateChild,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for LoginWindow {
+ const NAME: &'static str = "LoginWindow";
+ type Type = super::LoginWindow;
+ type ParentType = libadwaita::Window;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for LoginWindow {}
+ impl WidgetImpl for LoginWindow {}
+ impl AdwWindowImpl for LoginWindow {}
+ impl WindowImpl for LoginWindow {}
}
-impl LoginWidget {
- fn new() -> Self {
- Self::from_resource(resource!("/components/login.ui")).unwrap()
+glib::wrapper! {
+ pub struct LoginWindow(ObjectSubclass) @extends gtk::Widget, libadwaita::Window;
+}
+
+impl LoginWindow {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create an instance of LoginWindow")
+ }
+
+ fn connect_close(&self, on_close: F)
+ where
+ F: Fn() + 'static,
+ {
+ let widget = imp::LoginWindow::from_instance(self);
+ widget.close_button.connect_clicked(move |_| {
+ on_close();
+ });
+ }
+
+ fn connect_submit(&self, on_submit: SubmitFn)
+ where
+ SubmitFn: Fn(&str, &str) + Clone + 'static,
+ {
+ let widget = imp::LoginWindow::from_instance(self);
+
+ let on_submit_clone = on_submit.clone();
+ let controller = gtk::EventControllerKey::new();
+ controller.set_propagation_phase(gtk::PropagationPhase::Capture);
+ controller.connect_key_pressed(
+ clone!(@weak self as _self => @default-return gtk::Inhibit(false), move |_, key, _, _| {
+ if key == gdk::keys::constants::Return {
+ _self.submit(&on_submit_clone);
+ gtk::Inhibit(true)
+ } else {
+ gtk::Inhibit(false)
+ }
+ }),
+ );
+ self.add_controller(&controller);
+
+ widget
+ .login_button
+ .connect_clicked(clone!(@weak self as _self => move |_| {
+ _self.submit(&on_submit);
+ }));
+ }
+
+ fn show_error(&self, shown: bool) {
+ let widget = imp::LoginWindow::from_instance(self);
+ widget.error_container.set_reveal_child(shown);
+ }
+
+ fn submit(&self, on_submit: &SubmitFn)
+ where
+ SubmitFn: Fn(&str, &str),
+ {
+ let widget = imp::LoginWindow::from_instance(self);
+
+ self.show_error(false);
+
+ let username_text = widget.username.text();
+ let password_text = widget.password.text();
+
+ if username_text.is_empty() {
+ widget.username.grab_focus();
+ } else if password_text.is_empty() {
+ widget.password.grab_focus();
+ } else {
+ on_submit(username_text.as_str(), password_text.as_str());
+ }
}
}
pub struct Login {
parent: gtk::Window,
- window: libhandy::Window,
- error_container: gtk::Revealer,
+ login_window: LoginWindow,
model: Rc,
}
impl Login {
pub fn new(parent: gtk::Window, model: LoginModel) -> Self {
let model = Rc::new(model);
- let LoginWidget {
- window,
- username,
- password,
- close_button,
- login_button,
- error_container,
- } = LoginWidget::new();
-
- login_button.connect_clicked(
- clone!(@weak username, @weak password, @weak error_container, @weak model => move |_| {
- Self::submit_login_form(username, password, error_container, model);
- }),
- );
- username.connect_key_press_event(
- clone!(@weak username, @weak password, @weak error_container, @weak model => @default-return Inhibit(false), move |_, event | {
- Self::handle_keypress(username, password, error_container, model, event)
- }),
- );
+ let login_window = LoginWindow::new();
- password.connect_key_press_event(
- clone!(@weak username, @weak password, @weak error_container, @weak model => @default-return Inhibit(false), move |_, event | {
- Self::handle_keypress(username, password, error_container, model, event)
- }),
- );
-
- close_button.connect_clicked(clone!(@weak parent => move |_| {
+ login_window.connect_close(clone!(@weak parent => move || {
if let Some(app) = parent.application().as_ref() {
app.quit();
}
}));
+ login_window.connect_submit(clone!(@weak model => move |username, password| {
+ model.login(username.to_string(), password.to_string());
+ }));
+
Self {
parent,
- window,
- error_container,
+ login_window,
model,
}
}
+ fn window(&self) -> &libadwaita::Window {
+ self.login_window.upcast_ref::()
+ }
+
fn show_self_if_needed(&self) {
if self.model.try_autologin() {
- self.window.close();
+ self.window().close();
} else {
self.show_self();
}
}
fn show_self(&self) {
- self.window.set_transient_for(Some(&self.parent));
- self.window.set_modal(true);
- self.window.show_all();
+ self.window().set_transient_for(Some(&self.parent));
+ self.window().set_modal(true);
+ self.window().show();
}
fn hide_and_save_creds(&self, credentials: Credentials) {
- self.window.hide();
+ self.window().hide();
self.model.save_for_autologin(credentials);
}
- fn handle_keypress(
- username: gtk::Entry,
- password: gtk::Entry,
- error_container: gtk::Revealer,
- model: Rc,
- event: &EventKey,
- ) -> Inhibit {
- if event.keyval() == Return {
- Login::submit_login_form(username, password, error_container, model);
- Inhibit(true)
- } else {
- Inhibit(false)
- }
- }
-
- fn submit_login_form(
- username: gtk::Entry,
- password: gtk::Entry,
- error_container: gtk::Revealer,
- model: Rc,
- ) {
- error_container.set_reveal_child(false);
- let username_text = username.text().as_str().to_string();
- let password_text = password.text().as_str().to_string();
- if username_text.is_empty() {
- username.grab_focus();
- } else if password_text.is_empty() {
- password.grab_focus();
- } else {
- model.login(username_text, password_text);
- }
- }
-
fn reveal_error(&self) {
- self.error_container.set_reveal_child(true);
+ self.login_window.show_error(true);
}
}
diff --git a/src/app/components/login/login.ui b/src/app/components/login/login.ui
index c8e2edc8..a13b46ce 100644
--- a/src/app/components/login/login.ui
+++ b/src/app/components/login/login.ui
@@ -1,20 +1,14 @@
-
-
-
-
- False
+
+
+
360
100
-
- True
- False
+
- True
- False
4
4
4
@@ -22,188 +16,111 @@
vertical
- True
- True
- True
+ center
+ 1
end
- none
-
-
- 22
- 22
- True
- False
- window-close-symbolic
-
-
+ 0
+ 22
+ 22
+ window-close-symbolic
-
- False
- False
- 0
-
-
- True
- False
+
280
280
- True
- False
center
- True
+ 1
vertical
20
- True
- False
start
start
- Login to Spotify Premium
- True
+ Login to Spotify Premium
+ 1
0
0
-
+
-
- False
- True
- 0
-
- True
- False
vertical
4
- True
- True
avatar-default-symbolic
Username
-
- False
- True
- 0
-
- True
- True
- False
+ 0
●
dialog-password-symbolic
Password
-
- False
- True
- 1
-
-
- False
- True
- 1
-
- True
- False
+ 1
slide-up
-
+
- True
- False
8
- True
- False
+ center
start
2
- True
+ 1
dialog-warning-symbolic
-
- False
- False
- 0
-
- True
- False
Authentication failed!
- True
+ 1
0
0
-
+
-
- False
- True
- 1
-
-
+
-
- True
- True
- 2
-
Log in
- True
- True
- True
+ 1
-
- False
- True
- 3
-
-
- False
- True
- 1
-
-
-
+
+
\ No newline at end of file
diff --git a/src/app/components/mod.rs b/src/app/components/mod.rs
index 415b0d40..b114fb37 100644
--- a/src/app/components/mod.rs
+++ b/src/app/components/mod.rs
@@ -6,7 +6,6 @@ macro_rules! resource {
}
use gettextrs::*;
-use gtk::prelude::*;
use std::cell::RefCell;
use std::collections::HashSet;
use std::future::Future;
@@ -124,8 +123,8 @@ pub fn screen_add_css_provider(resource: &'static str) {
let provider = gtk::CssProvider::new();
provider.load_from_resource(resource);
- gtk::StyleContext::add_provider_for_screen(
- &gdk::Screen::default().unwrap(),
+ gtk::StyleContext::add_provider_for_display(
+ &gdk::Display::default().unwrap(),
&provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
diff --git a/src/app/components/navigation/home.rs b/src/app/components/navigation/home.rs
index cd3bfba9..0a714017 100644
--- a/src/app/components/navigation/home.rs
+++ b/src/app/components/navigation/home.rs
@@ -4,11 +4,11 @@ use gtk::prelude::*;
use crate::app::components::{Component, EventListener, ScreenFactory};
use crate::app::AppEvent;
-fn find_listbox_descendant(w: >k::Widget) -> Option {
- match w.clone().downcast::() {
- Ok(listbox) => Some(listbox),
- Err(widget) => {
- let next = widget.downcast::().ok()?.child()?;
+fn find_listbox_descendant(widget: >k::Widget) -> Option {
+ match widget.downcast_ref::() {
+ Some(listbox) => Some(listbox.clone()),
+ None => {
+ let next = widget.first_child()?;
find_listbox_descendant(&next)
}
}
@@ -29,16 +29,20 @@ impl HomePane {
let stack = gtk::Stack::new();
stack.set_transition_type(gtk::StackTransitionType::Crossfade);
// translators: This is a sidebar entry to browse to saved albums.
- stack.add_titled(library.get_root_widget(), "library", &gettext("Library"));
+ stack.add_titled(
+ library.get_root_widget(),
+ Some("library"),
+ &gettext("Library"),
+ );
stack.add_titled(
saved_playlists.get_root_widget(),
- "saved_playlists",
+ Some("saved_playlists"),
// translators: This is a sidebar entry to browse to saved playlists.
&gettext("Playlists"),
);
stack.add_titled(
now_playing.get_root_widget(),
- "now_playing",
+ Some("now_playing"),
&gettext("Now playing"),
);
diff --git a/src/app/components/navigation/navigation.rs b/src/app/components/navigation/navigation.rs
index 77bb8420..6a5d9109 100644
--- a/src/app/components/navigation/navigation.rs
+++ b/src/app/components/navigation/navigation.rs
@@ -1,6 +1,5 @@
use gtk::prelude::*;
-use libhandy::traits::LeafletExt;
-use libhandy::NavigationDirection;
+use libadwaita::NavigationDirection;
use std::rc::Rc;
use crate::app::components::{EventListener, ListenerComponent};
@@ -11,7 +10,7 @@ use super::{factory::ScreenFactory, home::HomePane, NavigationModel};
pub struct Navigation {
model: Rc,
- leaflet: libhandy::Leaflet,
+ leaflet: libadwaita::Leaflet,
navigation_stack: gtk::Stack,
home_stack_sidebar: gtk::StackSidebar,
back_button: gtk::Button,
@@ -22,7 +21,7 @@ pub struct Navigation {
impl Navigation {
pub fn new(
model: NavigationModel,
- leaflet: libhandy::Leaflet,
+ leaflet: libadwaita::Leaflet,
back_button: gtk::Button,
navigation_stack: gtk::Stack,
home_stack_sidebar: gtk::StackSidebar,
@@ -55,7 +54,7 @@ impl Navigation {
fn update_back_button(
back_button: >k::Button,
- leaflet: &libhandy::Leaflet,
+ leaflet: &libadwaita::Leaflet,
model: &Rc,
) {
let is_main = leaflet
@@ -67,7 +66,7 @@ impl Navigation {
fn connect_back_button(
back_button: >k::Button,
- leaflet: &libhandy::Leaflet,
+ leaflet: &libadwaita::Leaflet,
model: &Rc,
) {
back_button.connect_clicked(clone!(@weak leaflet, @weak model => move |_| {
@@ -116,10 +115,9 @@ impl Navigation {
};
let widget = component.get_root_widget();
- widget.show_all();
self.navigation_stack
- .add_named(widget, name.identifier().as_ref());
+ .add_named(widget, Some(name.identifier().as_ref()));
self.children.push(component);
self.navigation_stack
.set_visible_child_name(name.identifier().as_ref());
diff --git a/src/app/components/now_playing/now_playing.rs b/src/app/components/now_playing/now_playing.rs
index af2c34b4..f86f8548 100644
--- a/src/app/components/now_playing/now_playing.rs
+++ b/src/app/components/now_playing/now_playing.rs
@@ -1,5 +1,6 @@
-use gladis::Gladis;
use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::CompositeTemplate;
use std::rc::Rc;
use crate::app::components::{screen_add_css_provider, Component, EventListener, Playlist};
@@ -7,16 +8,67 @@ use crate::app::{state::PlaybackEvent, AppEvent};
use super::NowPlayingModel;
-#[derive(Clone, Gladis)]
-struct NowPlayingWidget {
- root: gtk::Widget,
- listbox: gtk::ListBox,
+mod imp {
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/now_playing.ui")]
+ pub struct NowPlayingWidget {
+ #[template_child]
+ pub song_list: TemplateChild,
+
+ #[template_child]
+ pub scrolled_window: TemplateChild,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for NowPlayingWidget {
+ const NAME: &'static str = "NowPlayingWidget";
+ type Type = super::NowPlayingWidget;
+ type ParentType = gtk::Box;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for NowPlayingWidget {}
+ impl WidgetImpl for NowPlayingWidget {}
+ impl BoxImpl for NowPlayingWidget {}
+}
+
+glib::wrapper! {
+ pub struct NowPlayingWidget(ObjectSubclass) @extends gtk::Widget, gtk::Box;
}
impl NowPlayingWidget {
fn new() -> Self {
screen_add_css_provider(resource!("/components/now_playing.css"));
- Self::from_resource(resource!("/components/now_playing.ui")).unwrap()
+ glib::Object::new(&[]).expect("Failed to create an instance of NowPlayingWidget")
+ }
+
+ fn connect_bottom_edge(&self, f: F)
+ where
+ F: Fn() + 'static,
+ {
+ imp::NowPlayingWidget::from_instance(self)
+ .scrolled_window
+ .connect_edge_reached(move |_, pos| {
+ if let gtk::PositionType::Bottom = pos {
+ f()
+ }
+ });
+ }
+
+ fn song_list_widget(&self) -> >k::ListView {
+ imp::NowPlayingWidget::from_instance(self)
+ .song_list
+ .as_ref()
}
}
@@ -29,7 +81,12 @@ pub struct NowPlaying {
impl NowPlaying {
pub fn new(model: Rc) -> Self {
let widget = NowPlayingWidget::new();
- let playlist = Playlist::new(widget.listbox.clone(), model.clone());
+
+ widget.connect_bottom_edge(clone!(@weak model => move || {
+ model.load_more();
+ }));
+
+ let playlist = Playlist::new(widget.song_list_widget().clone(), model.clone());
Self {
widget,
@@ -41,7 +98,7 @@ impl NowPlaying {
impl Component for NowPlaying {
fn get_root_widget(&self) -> >k::Widget {
- &self.widget.root
+ self.widget.upcast_ref()
}
fn get_children(&mut self) -> Option<&mut Vec>> {
diff --git a/src/app/components/now_playing/now_playing.ui b/src/app/components/now_playing/now_playing.ui
index b2a62724..e339db46 100644
--- a/src/app/components/now_playing/now_playing.ui
+++ b/src/app/components/now_playing/now_playing.ui
@@ -1,78 +1,40 @@
-
-
-
- True
- False
+
+
vertical
- True
- False
8
8
8
8
- True
- False
Now playing
end
+ 1
+ start
-
- False
- True
- 0
-
-
- False
- True
- 0
-
-
- True
- False
-
-
- False
- True
- 1
-
+
-
- True
- True
+
+ 1
-
- True
- False
-
-
- True
- False
-
-
-
+
-
- True
- True
- 2
-
-
+
diff --git a/src/app/components/now_playing/now_playing_model.rs b/src/app/components/now_playing/now_playing_model.rs
index 71c32b27..0f80905c 100644
--- a/src/app/components/now_playing/now_playing_model.rs
+++ b/src/app/components/now_playing/now_playing_model.rs
@@ -8,6 +8,7 @@ use std::sync::Arc;
use crate::app::components::{labels, PlaylistModel, SelectionTool, SelectionToolsModel};
use crate::app::models::SongDescription;
use crate::app::models::SongModel;
+use crate::app::state::PlaylistChange;
use crate::app::state::{
PlaybackAction, PlaybackEvent, PlaybackState, PlaylistSource, SelectionAction,
SelectionContext, SelectionState,
@@ -38,10 +39,15 @@ impl NowPlayingModel {
pub fn load_more_if_needed(&self) -> Option<()> {
let queue = self.queue();
- if !queue.exhausted() {
- return None;
+ if queue.exhausted() {
+ self.load_more()
+ } else {
+ None
}
+ }
+ pub fn load_more(&self) -> Option<()> {
+ let queue = self.queue();
let api = self.app_model.get_spotify();
let batch = queue.next_batch()?;
let batch_size = batch.batch_size;
@@ -73,16 +79,15 @@ impl PlaylistModel for NowPlayingModel {
fn diff_for_event(&self, event: &AppEvent) -> Option> {
let queue = self.queue();
- let offset = queue.current_offset().unwrap_or(0);
- let songs = queue
- .songs()
- .enumerate()
- .map(|(i, s)| s.to_song_model(offset + i));
+ let songs = queue.songs().enumerate().map(|(i, s)| s.to_song_model(i));
match event {
- AppEvent::PlaybackEvent(PlaybackEvent::PlaylistChanged) => {
- Some(ListDiff::Set(songs.collect()))
- }
+ AppEvent::PlaybackEvent(PlaybackEvent::PlaylistChanged(change)) => match change {
+ PlaylistChange::Reset => Some(ListDiff::Set(songs.collect())),
+ PlaylistChange::AppendedAt(i) => Some(ListDiff::Append(songs.skip(*i).collect())),
+ PlaylistChange::MovedDown(i) => Some(ListDiff::MoveDown(*i)),
+ PlaylistChange::MovedUp(i) => Some(ListDiff::MoveUp(*i)),
+ },
_ => None,
}
}
@@ -114,7 +119,11 @@ impl PlaylistModel for NowPlayingModel {
menu.append(Some(&*labels::VIEW_ALBUM), Some("song.view_album"));
for artist in song.artists.iter() {
menu.append(
- Some(&format!("{} {}", *labels::MORE_FROM, artist.name)),
+ Some(&format!(
+ "{} {}",
+ *labels::MORE_FROM,
+ glib::markup_escape_text(&artist.name)
+ )),
Some(&format!("song.view_artist_{}", artist.id)),
);
}
diff --git a/src/app/components/playback/component.rs b/src/app/components/playback/component.rs
new file mode 100644
index 00000000..73e16269
--- /dev/null
+++ b/src/app/components/playback/component.rs
@@ -0,0 +1,159 @@
+use std::ops::Deref;
+use std::rc::Rc;
+
+use crate::app::components::EventListener;
+use crate::app::models::SongDescription;
+use crate::app::state::{PlaybackAction, PlaybackEvent, RepeatMode, ScreenName};
+use crate::app::{
+ ActionDispatcher, AppAction, AppEvent, AppModel, AppState, BrowserAction, Worker,
+};
+
+use super::playback_widget::PlaybackWidget;
+
+pub struct PlaybackModel {
+ app_model: Rc,
+ dispatcher: Box,
+}
+
+impl PlaybackModel {
+ pub fn new(app_model: Rc, dispatcher: Box) -> Self {
+ Self {
+ app_model,
+ dispatcher,
+ }
+ }
+
+ fn state(&self) -> impl Deref + '_ {
+ self.app_model.get_state()
+ }
+
+ fn go_home(&self) {
+ self.dispatcher.dispatch(AppAction::ViewNowPlaying);
+ self.dispatcher
+ .dispatch(BrowserAction::NavigationPopTo(ScreenName::Home).into());
+ }
+
+ fn is_playing(&self) -> bool {
+ self.state().playback.is_playing()
+ }
+
+ fn is_shuffled(&self) -> bool {
+ self.state().playback.is_shuffled()
+ }
+
+ fn current_song(&self) -> Option + '_> {
+ self.app_model.map_state_opt(|s| s.playback.current_song())
+ }
+
+ fn play_next_song(&self) {
+ self.dispatcher.dispatch(PlaybackAction::Next.into());
+ }
+
+ fn play_prev_song(&self) {
+ self.dispatcher.dispatch(PlaybackAction::Previous.into());
+ }
+
+ fn toggle_playback(&self) {
+ self.dispatcher.dispatch(PlaybackAction::TogglePlay.into());
+ }
+
+ fn toggle_shuffle(&self) {
+ self.dispatcher
+ .dispatch(PlaybackAction::ToggleShuffle.into());
+ }
+
+ fn toggle_repeat(&self) {
+ self.dispatcher
+ .dispatch(PlaybackAction::ToggleRepeat.into());
+ }
+
+ fn seek_to(&self, position: u32) {
+ self.dispatcher
+ .dispatch(PlaybackAction::Seek(position).into());
+ }
+}
+
+pub struct PlaybackControl {
+ model: Rc,
+ widget: PlaybackWidget,
+ worker: Worker,
+}
+
+impl PlaybackControl {
+ pub fn new(model: PlaybackModel, widget: PlaybackWidget, worker: Worker) -> Self {
+ let model = Rc::new(model);
+
+ widget.connect_play_pause(clone!(@weak model => move || model.toggle_playback() ));
+ widget.connect_next(clone!(@weak model => move || model.play_next_song()));
+ widget.connect_prev(clone!(@weak model => move || model.play_prev_song()));
+ widget.connect_shuffle(clone!(@weak model => move || model.toggle_shuffle()));
+ widget.connect_repeat(clone!(@weak model => move || model.toggle_repeat()));
+ widget.connect_seek(clone!(@weak model => move |position| model.seek_to(position)));
+ widget.connect_now_playing_clicked(clone!(@weak model => move || model.go_home()));
+
+ Self {
+ model,
+ widget,
+ worker,
+ }
+ }
+
+ fn update_repeat(&self, mode: &RepeatMode) {
+ self.widget.set_repeat_mode(*mode);
+ }
+
+ fn update_shuffled(&self) {
+ self.widget.set_shuffled(self.model.is_shuffled());
+ }
+
+ fn update_playing(&self) {
+ let is_playing = self.model.is_playing();
+ self.widget.set_playing(is_playing);
+ }
+
+ fn update_current_info(&self) {
+ if let Some(song) = self.model.current_song() {
+ self.widget
+ .set_title_and_artist(&song.title, &song.artists_name());
+ self.widget.set_song_duration(Some(song.duration as f64));
+ if let Some(url) = song.art.clone() {
+ self.widget.set_artwork_from_url(url, &self.worker);
+ }
+ } else {
+ self.widget.reset_info();
+ }
+ }
+
+ fn sync_seek(&self, pos: u32) {
+ self.widget.set_seek_position(pos as f64);
+ }
+}
+
+impl EventListener for PlaybackControl {
+ fn on_event(&mut self, event: &AppEvent) {
+ match event {
+ AppEvent::PlaybackEvent(PlaybackEvent::PlaybackPaused)
+ | AppEvent::PlaybackEvent(PlaybackEvent::PlaybackResumed) => {
+ self.update_playing();
+ }
+ AppEvent::PlaybackEvent(PlaybackEvent::RepeatModeChanged(mode)) => {
+ self.update_repeat(mode);
+ }
+ AppEvent::PlaybackEvent(PlaybackEvent::ShuffleChanged) => {
+ self.update_shuffled();
+ }
+ AppEvent::PlaybackEvent(PlaybackEvent::TrackChanged(_)) => {
+ self.update_current_info();
+ }
+ AppEvent::PlaybackEvent(PlaybackEvent::PlaybackStopped) => {
+ self.update_playing();
+ self.update_current_info();
+ }
+ AppEvent::PlaybackEvent(PlaybackEvent::SeekSynced(pos))
+ | AppEvent::PlaybackEvent(PlaybackEvent::TrackSeeked(pos)) => {
+ self.sync_seek(*pos);
+ }
+ _ => {}
+ }
+ }
+}
diff --git a/src/app/components/playback/mod.rs b/src/app/components/playback/mod.rs
index 3bf32aca..2800d3f7 100644
--- a/src/app/components/playback/mod.rs
+++ b/src/app/components/playback/mod.rs
@@ -1,5 +1,11 @@
-mod playback_control;
-pub use playback_control::*;
-
+mod component;
+mod playback_controls;
mod playback_info;
-pub use playback_info::*;
+mod playback_widget;
+pub use component::*;
+
+use glib::prelude::*;
+
+pub fn expose_widgets() {
+ playback_widget::PlaybackWidget::static_type();
+}
diff --git a/src/app/components/playback/playback.css b/src/app/components/playback/playback.css
new file mode 100644
index 00000000..affd77f8
--- /dev/null
+++ b/src/app/components/playback/playback.css
@@ -0,0 +1,32 @@
+.seek-bar {
+ padding: 0;
+ padding-bottom: 2px;
+ min-height: 1px;
+}
+
+.seek-bar trough, .seek-bar highlight {
+ border-radius: 0;
+ border-left: none;
+ border-right: none;
+ min-height: 1px;
+ transition: min-height 100ms ease;
+}
+
+.seek-bar--active trough, .seek-bar--active highlight {
+ min-height: 5px;
+}
+
+.seek-bar--active:hover trough, .seek-bar--active:hover highlight {
+ min-height: 10px;
+}
+
+.seek-bar highlight {
+ border-left: none;
+ border-right: none;
+}
+
+.playback-button {
+ -gtk-icon-size: 28px;
+ min-width: 40px;
+ min-height: 40px;
+}
\ No newline at end of file
diff --git a/src/app/components/playback/playback_control.rs b/src/app/components/playback/playback_control.rs
deleted file mode 100644
index a2ce2f65..00000000
--- a/src/app/components/playback/playback_control.rs
+++ /dev/null
@@ -1,269 +0,0 @@
-use glib::signal;
-use gtk::prelude::*;
-use std::ops::Deref;
-use std::rc::Rc;
-
-use crate::app::components::utils::format_duration;
-use crate::app::components::{
- utils::{Clock, Debouncer},
- EventListener,
-};
-use crate::app::state::{PlaybackAction, PlaybackEvent, RepeatMode};
-use crate::app::{ActionDispatcher, AppEvent, AppModel, AppState};
-
-pub struct PlaybackControlModel {
- app_model: Rc,
- dispatcher: Box,
-}
-
-impl PlaybackControlModel {
- pub fn new(app_model: Rc, dispatcher: Box) -> Self {
- Self {
- app_model,
- dispatcher,
- }
- }
-
- fn state(&self) -> impl Deref + '_ {
- self.app_model.get_state()
- }
-
- pub fn is_playing(&self) -> bool {
- self.state().playback.is_playing()
- }
-
- pub fn current_song_duration(&self) -> Option {
- self.state()
- .playback
- .current_song()
- .map(|s| s.duration as f64)
- }
-
- pub fn play_next_song(&self) {
- self.dispatcher.dispatch(PlaybackAction::Next.into());
- }
-
- pub fn play_prev_song(&self) {
- self.dispatcher.dispatch(PlaybackAction::Previous.into());
- }
-
- pub fn toggle_playback(&self) {
- self.dispatcher.dispatch(PlaybackAction::TogglePlay.into());
- }
-
- pub fn toggle_shuffle(&self) {
- self.dispatcher
- .dispatch(PlaybackAction::ToggleShuffle.into());
- }
-
- pub fn toggle_repeat(&self) {
- self.dispatcher
- .dispatch(PlaybackAction::ToggleRepeat.into());
- }
-
- pub fn seek_to(&self, position: u32) {
- self.dispatcher
- .dispatch(PlaybackAction::Seek(position).into());
- }
-}
-
-pub struct PlaybackControlWidget {
- play_button: gtk::Button,
- seek_bar: gtk::Scale,
- track_position: gtk::Label,
- track_duration: gtk::Label,
- next: gtk::Button,
- prev: gtk::Button,
- shuffle_button: gtk::Button,
- repeat_button: gtk::Button,
-}
-
-impl PlaybackControlWidget {
- #[allow(clippy::too_many_arguments)]
- pub fn new(
- play_button: gtk::Button,
- seek_bar: gtk::Scale,
- track_position: gtk::Label,
- track_duration: gtk::Label,
- next: gtk::Button,
- prev: gtk::Button,
- shuffle_button: gtk::Button,
- repeat_button: gtk::Button,
- ) -> Self {
- Self {
- play_button,
- seek_bar,
- track_position,
- track_duration,
- next,
- prev,
- shuffle_button,
- repeat_button,
- }
- }
-}
-
-pub struct PlaybackControl {
- model: Rc,
- widget: PlaybackControlWidget,
- _debouncer: Debouncer,
- clock: Clock,
-}
-
-const STEP: f64 = 5000.0;
-impl PlaybackControl {
- pub fn new(model: PlaybackControlModel, widget: PlaybackControlWidget) -> Self {
- let model = Rc::new(model);
- let debouncer = Debouncer::new();
- let debouncer_clone = debouncer.clone();
- let track_position = &widget.track_position;
- widget.seek_bar.set_increments(STEP, STEP);
- widget.seek_bar.connect_change_value(
- clone!(@weak model, @weak track_position => @default-return signal::Inhibit(false), move |_, _, requested| {
- track_position.set_text(&format_duration(requested));
- debouncer_clone.debounce(200, move || {
- model.seek_to(requested as u32);
- });
- signal::Inhibit(false)
- }),
- );
-
- widget
- .play_button
- .connect_clicked(clone!(@weak model => move |_| {
- model.toggle_playback();
- }));
-
- widget.next.connect_clicked(clone!(@weak model => move |_| {
- model.play_next_song();
- }));
-
- widget.prev.connect_clicked(clone!(@weak model => move |_| {
- model.play_prev_song();
- }));
-
- widget
- .shuffle_button
- .connect_clicked(clone!(@weak model => move |_| {
- model.toggle_shuffle();
- }));
-
- widget
- .repeat_button
- .connect_clicked(clone!(@weak model => move |_| {
- model.toggle_repeat();
- }));
-
- Self {
- model,
- widget,
- _debouncer: debouncer,
- clock: Clock::new(),
- }
- }
-
- fn set_playing(&self, is_playing: bool) {
- let playback_image = if is_playing {
- "media-playback-pause-symbolic"
- } else {
- "media-playback-start-symbolic"
- };
-
- self.widget
- .play_button
- .child()
- .and_then(|child| child.downcast::().ok())
- .map(|image| {
- image.set_from_icon_name(Some(playback_image), image.icon_size());
- })
- .expect("error updating icon");
- }
-
- fn update_repeat(&self, mode: &RepeatMode) {
- let playback_image = match mode {
- RepeatMode::Song => "media-playlist-repeat-song-symbolic.symbolic",
- RepeatMode::Playlist => "media-playlist-repeat-symbolic",
- RepeatMode::None => "media-playlist-consecutive-symbolic.symbolic",
- };
-
- self.widget
- .repeat_button
- .child()
- .and_then(|child| child.downcast::().ok())
- .map(|image| {
- image.set_from_icon_name(Some(playback_image), image.icon_size());
- })
- .expect("error updating icon");
- }
-
- fn update_playing(&self) {
- let is_playing = self.model.is_playing();
- self.set_playing(is_playing);
-
- if is_playing {
- let seek_bar = &self.widget.seek_bar;
- let track_position = &self.widget.track_position;
- self.clock
- .start(clone!(@weak seek_bar, @weak track_position => move || {
- let value = seek_bar.value() + 1000.0;
- seek_bar.set_value(value);
- track_position.set_text(&format_duration(value));
- }));
- } else {
- self.clock.stop();
- }
- }
-
- fn update_current_info(&self) {
- let class = "seek-bar--active";
- let style_context = self.widget.seek_bar.style_context();
- if let Some(duration) = self.model.current_song_duration() {
- style_context.add_class(class);
- self.widget.seek_bar.set_range(0.0, duration);
- self.widget.seek_bar.set_value(0.0);
- self.widget.track_position.set_text("0:00");
- self.widget
- .track_duration
- .set_text(&format!(" / {}", format_duration(duration)));
- self.widget.track_position.show();
- self.widget.track_duration.show();
- } else {
- style_context.remove_class(class);
- self.widget.seek_bar.set_range(0.0, 0.0);
- self.widget.track_position.hide();
- self.widget.track_duration.hide();
- }
- }
-
- fn sync_seek(&self, pos: u32) {
- let pos = pos as f64;
- self.widget.seek_bar.set_value(pos);
- self.widget.track_position.set_text(&format_duration(pos));
- }
-}
-
-impl EventListener for PlaybackControl {
- fn on_event(&mut self, event: &AppEvent) {
- match event {
- AppEvent::PlaybackEvent(PlaybackEvent::PlaybackPaused)
- | AppEvent::PlaybackEvent(PlaybackEvent::PlaybackResumed) => {
- self.update_playing();
- }
- AppEvent::PlaybackEvent(PlaybackEvent::TrackChanged(_)) => {
- self.update_current_info();
- }
- AppEvent::PlaybackEvent(PlaybackEvent::RepeatModeChanged(mode)) => {
- self.update_repeat(mode);
- }
- AppEvent::PlaybackEvent(PlaybackEvent::PlaybackStopped) => {
- self.update_playing();
- self.update_current_info();
- }
- AppEvent::PlaybackEvent(PlaybackEvent::SeekSynced(pos))
- | AppEvent::PlaybackEvent(PlaybackEvent::TrackSeeked(pos)) => {
- self.sync_seek(*pos);
- }
- _ => {}
- }
- }
-}
diff --git a/src/app/components/playback/playback_controls.rs b/src/app/components/playback/playback_controls.rs
new file mode 100644
index 00000000..47f4213e
--- /dev/null
+++ b/src/app/components/playback/playback_controls.rs
@@ -0,0 +1,129 @@
+use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::{glib, CompositeTemplate};
+
+use crate::app::state::RepeatMode;
+
+mod imp {
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/playback_controls.ui")]
+ pub struct PlaybackControlsWidget {
+ #[template_child]
+ pub play_pause: TemplateChild,
+
+ #[template_child]
+ pub next: TemplateChild,
+
+ #[template_child]
+ pub prev: TemplateChild,
+
+ #[template_child]
+ pub shuffle: TemplateChild,
+
+ #[template_child]
+ pub repeat: TemplateChild,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for PlaybackControlsWidget {
+ const NAME: &'static str = "PlaybackControlsWidget";
+ type Type = super::PlaybackControlsWidget;
+ type ParentType = gtk::Box;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for PlaybackControlsWidget {}
+ impl WidgetImpl for PlaybackControlsWidget {}
+ impl BoxImpl for PlaybackControlsWidget {}
+}
+
+glib::wrapper! {
+ pub struct PlaybackControlsWidget(ObjectSubclass) @extends gtk::Widget, gtk::Box;
+}
+
+impl PlaybackControlsWidget {
+ pub fn set_playing(&self, is_playing: bool) {
+ let playback_icon = if is_playing {
+ "media-playback-pause-symbolic"
+ } else {
+ "media-playback-start-symbolic"
+ };
+
+ imp::PlaybackControlsWidget::from_instance(self)
+ .play_pause
+ .set_icon_name(playback_icon);
+ }
+
+ pub fn set_shuffled(&self, shuffled: bool) {
+ imp::PlaybackControlsWidget::from_instance(self)
+ .shuffle
+ .set_active(shuffled);
+ }
+
+ pub fn set_repeat_mode(&self, mode: RepeatMode) {
+ let repeat_mode_icon = match mode {
+ RepeatMode::Song => "media-playlist-repeat-song-symbolic",
+ RepeatMode::Playlist => "media-playlist-repeat-symbolic",
+ RepeatMode::None => "media-playlist-consecutive-symbolic",
+ };
+
+ imp::PlaybackControlsWidget::from_instance(self)
+ .repeat
+ .set_icon_name(repeat_mode_icon);
+ }
+
+ pub fn connect_play_pause(&self, f: F)
+ where
+ F: Fn() + 'static,
+ {
+ imp::PlaybackControlsWidget::from_instance(self)
+ .play_pause
+ .connect_clicked(move |_| f());
+ }
+
+ pub fn connect_prev(&self, f: F)
+ where
+ F: Fn() + 'static,
+ {
+ imp::PlaybackControlsWidget::from_instance(self)
+ .prev
+ .connect_clicked(move |_| f());
+ }
+
+ pub fn connect_next(&self, f: F)
+ where
+ F: Fn() + 'static,
+ {
+ imp::PlaybackControlsWidget::from_instance(self)
+ .next
+ .connect_clicked(move |_| f());
+ }
+
+ pub fn connect_shuffle(&self, f: F)
+ where
+ F: Fn() + 'static,
+ {
+ imp::PlaybackControlsWidget::from_instance(self)
+ .shuffle
+ .connect_clicked(move |_| f());
+ }
+
+ pub fn connect_repeat(&self, f: F)
+ where
+ F: Fn() + 'static,
+ {
+ imp::PlaybackControlsWidget::from_instance(self)
+ .repeat
+ .connect_clicked(move |_| f());
+ }
+}
diff --git a/src/app/components/playback/playback_controls.ui b/src/app/components/playback/playback_controls.ui
new file mode 100644
index 00000000..7695944b
--- /dev/null
+++ b/src/app/components/playback/playback_controls.ui
@@ -0,0 +1,59 @@
+
+
+
+
+
+ center
+ 1
+ 8
+ 1
+
+
+ 1
+ center
+ center
+ 0
+ media-playlist-shuffle-symbolic
+
+
+
+
+ 1
+ center
+ center
+ 0
+ media-skip-backward-symbolic
+
+
+
+
+ 1
+ center
+ center
+ media-playback-start-symbolic
+
+
+
+
+
+ 1
+ center
+ center
+ 0
+ media-skip-forward-symbolic
+
+
+
+
+ 1
+ center
+ center
+ 0
+ media-playlist-consecutive-symbolic
+
+
+
+
\ No newline at end of file
diff --git a/src/app/components/playback/playback_info.rs b/src/app/components/playback/playback_info.rs
index 54c613fc..a6d4dff6 100644
--- a/src/app/components/playback/playback_info.rs
+++ b/src/app/components/playback/playback_info.rs
@@ -1,107 +1,78 @@
-use gettextrs::*;
+use gettextrs::gettext;
use gtk::prelude::*;
-use std::ops::Deref;
-use std::rc::Rc;
+use gtk::subclass::prelude::*;
+use gtk::{glib, CompositeTemplate};
-use crate::app::components::EventListener;
-use crate::app::dispatch::Worker;
-use crate::app::loader::ImageLoader;
-use crate::app::models::*;
-use crate::app::state::{BrowserAction, PlaybackEvent, ScreenName};
-use crate::app::{ActionDispatcher, AppAction, AppEvent, AppModel};
+mod imp {
-pub struct PlaybackInfoModel {
- app_model: Rc,
- dispatcher: Box,
-}
+ use super::*;
-impl PlaybackInfoModel {
- pub fn new(app_model: Rc, dispatcher: Box) -> Self {
- Self {
- app_model,
- dispatcher,
- }
- }
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/playback_info.ui")]
+ pub struct PlaybackInfoWidget {
+ #[template_child]
+ pub playing_image: TemplateChild,
- fn current_song(&self) -> Option + '_> {
- self.app_model.map_state_opt(|s| s.playback.current_song())
+ #[template_child]
+ pub current_song_info: TemplateChild,
}
- fn go_home(&self) {
- self.dispatcher.dispatch(AppAction::ViewNowPlaying);
- self.dispatcher
- .dispatch(BrowserAction::NavigationPopTo(ScreenName::Home).into());
+ #[glib::object_subclass]
+ impl ObjectSubclass for PlaybackInfoWidget {
+ const NAME: &'static str = "PlaybackInfoWidget";
+ type Type = super::PlaybackInfoWidget;
+ type ParentType = gtk::Button;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
}
+
+ impl ObjectImpl for PlaybackInfoWidget {}
+ impl WidgetImpl for PlaybackInfoWidget {}
+ impl ButtonImpl for PlaybackInfoWidget {}
}
-pub struct PlaybackInfo {
- model: Rc,
- worker: Worker,
- current_song_image: gtk::Image,
- current_song_image_small: gtk::Image,
- current_song_info: gtk::Label,
+glib::wrapper! {
+ pub struct PlaybackInfoWidget(ObjectSubclass) @extends gtk::Widget, gtk::Button;
}
-impl PlaybackInfo {
- pub fn new(
- model: PlaybackInfoModel,
- worker: Worker,
- now_playing: gtk::Button,
- now_playing_small: gtk::Button,
- current_song_image: gtk::Image,
- current_song_image_small: gtk::Image,
- current_song_info: gtk::Label,
- ) -> Self {
- let model = Rc::new(model);
- now_playing.connect_clicked(clone!(@weak model => move |_| model.go_home()));
- now_playing_small.connect_clicked(clone!(@weak model => move |_| model.go_home()));
- Self {
- model,
- worker,
- current_song_image,
- current_song_image_small,
- current_song_info,
- }
+impl PlaybackInfoWidget {
+ pub fn set_title_and_artist(&self, title: &str, artist: &str) {
+ let widget = imp::PlaybackInfoWidget::from_instance(self);
+ let title = glib::markup_escape_text(title);
+ let artist = glib::markup_escape_text(artist);
+ let label = format!("{}\n{}", title.as_str(), artist.as_str());
+ widget.current_song_info.set_label(&label[..]);
}
- fn update_current_info(&self) {
- if let Some(song) = self.model.current_song() {
- let title = glib::markup_escape_text(&song.title);
- let artist = glib::markup_escape_text(&song.artists_name());
- let label = format!("{}\n{}", title.as_str(), artist.as_str());
- self.current_song_info.set_label(&label[..]);
-
- let image1 = self.current_song_image.downgrade();
- let image2 = self.current_song_image_small.downgrade();
+ pub fn reset_info(&self) {
+ let widget = imp::PlaybackInfoWidget::from_instance(self);
+ widget
+ .current_song_info
+ // translators: Short text displayed instead of a song title when nothing plays
+ .set_label(&gettext("No song playing"));
+ widget
+ .playing_image
+ .set_from_icon_name(Some("emblem-music-symbolic"));
+ widget
+ .playing_image
+ .set_from_icon_name(Some("emblem-music-symbolic"));
+ }
- if let Some(url) = song.art.clone() {
- self.worker.send_local_task(async move {
- let loader = ImageLoader::new();
- let result = loader.load_remote(&url, "jpg", 48, 48).await;
- if let (Some(image1), Some(image2)) = (image1.upgrade(), image2.upgrade()) {
- image1.set_from_pixbuf(result.as_ref());
- image2.set_from_pixbuf(result.as_ref());
- }
- });
- }
- } else {
- self.current_song_info
- // translators: Short text displayed instead of a song title when nothing plays
- .set_label(&gettext("No song playing"));
- self.current_song_image
- .set_from_icon_name(Some("emblem-music-symbolic"), gtk::IconSize::Button);
- self.current_song_image_small
- .set_from_icon_name(Some("emblem-music-symbolic"), gtk::IconSize::Button);
- }
+ pub fn set_info_visible(&self, visible: bool) {
+ imp::PlaybackInfoWidget::from_instance(self)
+ .current_song_info
+ .set_visible(visible);
}
-}
-impl EventListener for PlaybackInfo {
- fn on_event(&mut self, event: &AppEvent) {
- if let AppEvent::PlaybackEvent(PlaybackEvent::TrackChanged(_))
- | AppEvent::PlaybackEvent(PlaybackEvent::PlaybackStopped) = event
- {
- self.update_current_info();
- }
+ pub fn set_artwork(&self, art: &gdk_pixbuf::Pixbuf) {
+ imp::PlaybackInfoWidget::from_instance(self)
+ .playing_image
+ .set_from_pixbuf(Some(art));
}
}
diff --git a/src/app/components/playback/playback_info.ui b/src/app/components/playback/playback_info.ui
new file mode 100644
index 00000000..92d28ebc
--- /dev/null
+++ b/src/app/components/playback/playback_info.ui
@@ -0,0 +1,41 @@
+
+
+
+
+
+ 1
+ start
+ center
+ 0
+
+ 1
+ 0
+ 0
+
+
+
+ center
+
+
+ 40
+ 40
+ emblem-music-symbolic
+
+
+
+
+ 0
+ start
+ 1
+ 12
+ 12
+ No song playing
+ 1
+ middle
+ 1
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/components/playback/playback_widget.rs b/src/app/components/playback/playback_widget.rs
new file mode 100644
index 00000000..ede4b525
--- /dev/null
+++ b/src/app/components/playback/playback_widget.rs
@@ -0,0 +1,238 @@
+use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::{glib, CompositeTemplate};
+
+use crate::app::components::screen_add_css_provider;
+use crate::app::components::utils::{format_duration, Clock, Debouncer};
+use crate::app::loader::ImageLoader;
+use crate::app::state::RepeatMode;
+use crate::app::Worker;
+
+use super::playback_controls::PlaybackControlsWidget;
+use super::playback_info::PlaybackInfoWidget;
+
+mod imp {
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/playback_widget.ui")]
+ pub struct PlaybackWidget {
+ #[template_child]
+ pub controls: TemplateChild,
+
+ #[template_child]
+ pub controls_mobile: TemplateChild,
+
+ #[template_child]
+ pub now_playing: TemplateChild,
+
+ #[template_child]
+ pub now_playing_mobile: TemplateChild,
+
+ #[template_child]
+ pub seek_bar: TemplateChild,
+
+ #[template_child]
+ pub track_position: TemplateChild,
+
+ #[template_child]
+ pub track_duration: TemplateChild,
+
+ pub clock: Clock,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for PlaybackWidget {
+ const NAME: &'static str = "PlaybackWidget";
+ type Type = super::PlaybackWidget;
+ type ParentType = gtk::Box;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for PlaybackWidget {
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+ self.now_playing_mobile.set_info_visible(false);
+ self.now_playing.set_info_visible(true);
+ screen_add_css_provider(resource!("/components/playback.css"));
+ }
+ }
+
+ impl WidgetImpl for PlaybackWidget {}
+ impl BoxImpl for PlaybackWidget {}
+}
+
+glib::wrapper! {
+ pub struct PlaybackWidget(ObjectSubclass) @extends gtk::Widget, gtk::Box;
+}
+
+impl PlaybackWidget {
+ pub fn set_title_and_artist(&self, title: &str, artist: &str) {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.now_playing.set_title_and_artist(title, artist);
+ }
+
+ pub fn reset_info(&self) {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.now_playing.reset_info();
+ widget.now_playing_mobile.reset_info();
+ self.set_song_duration(None);
+ }
+
+ fn set_artwork(&self, image: &gdk_pixbuf::Pixbuf) {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.now_playing.set_artwork(image);
+ widget.now_playing_mobile.set_artwork(image);
+ }
+
+ pub fn set_artwork_from_url(&self, url: String, worker: &Worker) {
+ let weak_self = self.downgrade();
+ worker.send_local_task(async move {
+ let loader = ImageLoader::new();
+ let result = loader.load_remote(&url, "jpg", 48, 48).await;
+ if let (Some(ref _self), Some(ref result)) = (weak_self.upgrade(), result) {
+ _self.set_artwork(result);
+ }
+ });
+ }
+
+ pub fn set_song_duration(&self, duration: Option) {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ let class = "seek-bar--active";
+ let style_context = widget.seek_bar.style_context();
+ if let Some(duration) = duration {
+ style_context.add_class(class);
+ widget.seek_bar.set_range(0.0, duration);
+ widget.seek_bar.set_value(0.0);
+ widget.track_position.set_text("0:00");
+ widget
+ .track_duration
+ .set_text(&format!(" / {}", format_duration(duration)));
+ widget.track_position.show();
+ widget.track_duration.show();
+ } else {
+ style_context.remove_class(class);
+ widget.seek_bar.set_range(0.0, 0.0);
+ widget.track_position.hide();
+ widget.track_duration.hide();
+ }
+ }
+
+ pub fn set_seek_position(&self, pos: f64) {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.seek_bar.set_value(pos);
+ widget.track_position.set_text(&format_duration(pos));
+ }
+
+ pub fn increment_seek_position(&self) {
+ let value = imp::PlaybackWidget::from_instance(self).seek_bar.value() + 1_000.0;
+ self.set_seek_position(value);
+ }
+
+ pub fn connect_now_playing_clicked(&self, f: F)
+ where
+ F: Fn() + Clone + 'static,
+ {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ let f_clone = f.clone();
+ widget.now_playing.connect_clicked(move |_| f_clone());
+ widget.now_playing_mobile.connect_clicked(move |_| f());
+ }
+
+ pub fn connect_seek(&self, seek: Seek)
+ where
+ Seek: Fn(u32) + Clone + 'static,
+ {
+ let debouncer = Debouncer::new();
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.seek_bar.set_increments(5_000.0, 10_000.0);
+ widget.seek_bar.connect_change_value(
+ clone!(@weak self as _self => @default-return glib::signal::Inhibit(false), move |_, _, requested| {
+ imp::PlaybackWidget::from_instance(&_self)
+ .track_position
+ .set_text(&format_duration(requested));
+ let seek = seek.clone();
+ debouncer.debounce(200, move || seek(requested as u32));
+ glib::signal::Inhibit(false)
+ }),
+ );
+ }
+
+ pub fn set_playing(&self, is_playing: bool) {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.controls.set_playing(is_playing);
+ widget.controls_mobile.set_playing(is_playing);
+ if is_playing {
+ widget
+ .clock
+ .start(clone!(@weak self as _self => move || _self.increment_seek_position()));
+ } else {
+ widget.clock.stop();
+ }
+ }
+
+ pub fn set_repeat_mode(&self, mode: RepeatMode) {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.controls.set_repeat_mode(mode);
+ widget.controls_mobile.set_repeat_mode(mode);
+ }
+
+ pub fn set_shuffled(&self, shuffled: bool) {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.controls.set_shuffled(shuffled);
+ widget.controls_mobile.set_shuffled(shuffled);
+ }
+
+ pub fn connect_play_pause(&self, f: F)
+ where
+ F: Fn() + Clone + 'static,
+ {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.controls.connect_play_pause(f.clone());
+ widget.controls_mobile.connect_play_pause(f);
+ }
+
+ pub fn connect_prev(&self, f: F)
+ where
+ F: Fn() + Clone + 'static,
+ {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.controls.connect_prev(f.clone());
+ widget.controls_mobile.connect_prev(f);
+ }
+
+ pub fn connect_next(&self, f: F)
+ where
+ F: Fn() + Clone + 'static,
+ {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.controls.connect_next(f.clone());
+ widget.controls_mobile.connect_next(f);
+ }
+
+ pub fn connect_shuffle(&self, f: F)
+ where
+ F: Fn() + Clone + 'static,
+ {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.controls.connect_shuffle(f.clone());
+ widget.controls_mobile.connect_shuffle(f);
+ }
+
+ pub fn connect_repeat(&self, f: F)
+ where
+ F: Fn() + Clone + 'static,
+ {
+ let widget = imp::PlaybackWidget::from_instance(self);
+ widget.controls.connect_repeat(f.clone());
+ widget.controls_mobile.connect_repeat(f);
+ }
+}
diff --git a/src/app/components/playback/playback_widget.ui b/src/app/components/playback/playback_widget.ui
new file mode 100644
index 00000000..c02501dd
--- /dev/null
+++ b/src/app/components/playback/playback_widget.ui
@@ -0,0 +1,108 @@
+
+
+
+
+
+ vertical
+
+
+ 1
+ 0
+ 0
+ 0
+ left
+
+
+
+
+
+ 4
+ 4
+ 8
+ 8
+
+
+
+ 1
+ 1
+
+
+
+ 1
+ start
+ center
+ 0
+
+ 1
+ 0
+ 0
+
+
+
+
+
+
+
+ 1
+ 1
+ 0
+
+
+
+
+
+
+ 4
+ 4
+ 4
+ 4
+
+ 1
+ 2
+ 0
+
+
+
+ 0
+ 0:00
+ end
+ 1
+
+
+
+
+ 0
+ / 0:00
+ end
+
+
+
+
+
+
+
+
+
+ fill
+ 1
+
+
+ 1
+ start
+ center
+ 0
+
+
+
+
+ center
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/components/playlist/playlist.rs b/src/app/components/playlist/playlist.rs
index 857223d5..cdaccc08 100644
--- a/src/app/components/playlist/playlist.rs
+++ b/src/app/components/playlist/playlist.rs
@@ -3,8 +3,8 @@ use gtk::prelude::*;
use std::ops::Deref;
use std::rc::Rc;
-use crate::app::components::utils::{in_viewport, vscroll_to, AnimatorDefault};
-use crate::app::components::{Component, EventListener, Song};
+use crate::app::components::utils::AnimatorDefault;
+use crate::app::components::{Component, EventListener, SongWidget};
use crate::app::models::SongModel;
use crate::app::{
state::{PlaybackEvent, SelectionEvent, SelectionState},
@@ -51,36 +51,55 @@ pub trait PlaylistModel {
}
pub struct Playlist {
- listbox: gtk::ListBox,
+ animator: AnimatorDefault,
+ listview: gtk::ListView,
_press_gesture: gtk::GestureLongPress,
list_model: ListStore,
model: Rc,
- animator: AnimatorDefault,
}
impl Playlist
where
Model: PlaylistModel + 'static,
{
- pub fn new(listbox: gtk::ListBox, model: Rc) -> Self {
+ pub fn new(listview: gtk::ListView, model: Rc) -> Self {
let list_model = ListStore::new();
+ let selection_model = gtk::NoSelection::new(Some(list_model.unsafe_store()));
+ let factory = gtk::SignalListItemFactory::new();
- Self::set_selection_active(&listbox, model.is_selection_enabled());
- listbox.style_context().add_class("playlist");
- listbox.set_activate_on_single_click(true);
+ listview.set_factory(Some(&factory));
+ listview.style_context().add_class("playlist");
+ listview.set_single_click_activate(true);
+ listview.set_model(Some(&selection_model));
+ Self::set_selection_active(&listview, model.is_selection_enabled());
- let press_gesture = gtk::GestureLongPress::new(&listbox);
- listbox.add_events(gdk::EventMask::TOUCH_MASK);
- press_gesture.set_touch_only(false);
- press_gesture.set_propagation_phase(gtk::PropagationPhase::Capture);
- press_gesture.connect_pressed(clone!(@weak model => move |_, _, _| {
- model.enable_selection();
+ factory.connect_setup(|_, item| {
+ item.set_child(Some(&SongWidget::new()));
+ });
+
+ factory.connect_bind(clone!(@weak model => move |_, item| {
+ let song_model = item.item().unwrap().downcast::().unwrap();
+ song_model.set_state(Self::get_item_state(&*model, &song_model));
+
+ let widget = item.child().unwrap().downcast::().unwrap();
+ widget.bind(&song_model);
+
+ let id = &song_model.get_id();
+ widget.set_actions(model.actions_for(id).as_ref());
+ widget.set_menu(model.menu_for(id).as_ref());
}));
- let list_model_clone = list_model.clone();
- listbox.connect_row_activated(clone!(@weak model => move |_, row| {
- let index = row.index() as u32;
- let song: SongModel = list_model_clone.get(index);
+ factory.connect_unbind(|_, item| {
+ let song_model = item.item().unwrap().downcast::().unwrap();
+ song_model.unbind_all();
+
+ let widget = item.child().unwrap().downcast::().unwrap();
+ widget.set_actions(None);
+ widget.set_menu(None);
+ });
+
+ listview.connect_activate(clone!(@weak list_model, @weak model => move |_, position| {
+ let song = list_model.get(position);
let selection_enabled = model.is_selection_enabled();
if selection_enabled {
Self::select_song(&*model, &song);
@@ -89,36 +108,20 @@ where
}
}));
- listbox.bind_model(
- Some(list_model.unsafe_store()),
- clone!(@weak model, @weak listbox => @default-panic, move |item| {
- let item = item.downcast_ref::().unwrap();
- let id = &item.get_id();
-
- let row = gtk::ListBoxRow::new();
- let event_box = gtk::EventBox::new();
- row.add(&event_box);
-
- let song = Song::new(item.clone());
- event_box.add(song.get_root_widget());
-
- song.set_menu(model.menu_for(id).as_ref());
- song.set_actions(model.actions_for(id).as_ref());
-
- Self::set_row_state(&listbox, item, &row, Self::get_row_state(item, &*model, None));
- Self::connect_events(item, &row, model);
-
- row.show_all();
- row.upcast::()
- }),
- );
+ let press_gesture = gtk::GestureLongPress::new();
+ listview.add_controller(&press_gesture);
+ press_gesture.set_touch_only(false);
+ press_gesture.set_propagation_phase(gtk::PropagationPhase::Capture);
+ press_gesture.connect_pressed(clone!(@weak model => move |_, _, _| {
+ model.enable_selection();
+ }));
Self {
- listbox,
+ animator: AnimatorDefault::ease_in_out_animator(),
+ listview,
_press_gesture: press_gesture,
list_model,
model,
- animator: AnimatorDefault::ease_in_out_animator(),
}
}
@@ -132,29 +135,9 @@ where
}
}
- fn connect_events(item: &SongModel, row: >k::ListBoxRow, model: Rc) {
- row.connect_button_release_event(
- clone!(@weak model, @strong item => @default-return Inhibit(false), move |_, event| {
- if event.button() == 3 && model.enable_selection() {
- Self::select_song(&*model, &item);
- Inhibit(true)
- } else {
- Inhibit(false)
- }
- }),
- );
- }
-
- fn get_row_state(
- item: &SongModel,
- model: &Model,
- current_song_id: Option<&String>,
- ) -> RowState {
+ fn get_item_state(model: &Model, item: &SongModel) -> RowState {
let id = &item.get_id();
- let is_playing = current_song_id
- .map(|s| s.eq(id))
- .or_else(|| Some(model.current_song_id()?.eq(id)))
- .unwrap_or(false);
+ let is_playing = model.current_song_id().map(|s| s.eq(id)).unwrap_or(false);
let is_selected = model
.selection()
.map(|s| s.is_song_selected(id))
@@ -165,56 +148,59 @@ where
}
}
- fn set_row_state(
- listbox: >k::ListBox,
- item: &SongModel,
- row: >k::ListBoxRow,
- state: RowState,
- ) {
- item.set_playing(state.is_playing);
- item.set_selected(state.is_selected);
- if state.is_selected {
- row.set_selectable(true);
- listbox.select_row(Some(row));
- } else {
- row.set_selectable(false);
+ fn autoscroll_to_playing(&self, index: usize) {
+ let len = self.list_model.len() as f64;
+ let adj = self
+ .listview
+ .parent()
+ .and_then(|p| p.downcast::().ok())
+ .and_then(|w| w.vadjustment());
+ if let Some(adj) = adj {
+ let v = adj.value();
+ let pos = (index as f64) * adj.upper() / len;
+ if pos < v || pos > v + 0.9 * adj.page_size() {
+ self.animator.animate(
+ 20,
+ clone!(@weak adj => @default-return false, move |p| {
+ let v = adj.value();
+ adj.set_value(v + p * (pos - v));
+ true
+ }),
+ );
+ }
}
}
- fn rows_and_songs(&self) -> impl Iterator- + '_ {
- let listbox = &self.listbox;
- self.list_model
- .iter()
- .enumerate()
- .filter_map(move |(i, song)| listbox.row_at_index(i as i32).map(|r| (r, song)))
- }
-
- fn update_list(&self, scroll: bool) {
- let autoscroll = scroll && self.model.autoscroll_to_playing();
- let current_song_id = self.model.current_song_id();
- for (row, model_song) in self.rows_and_songs() {
- let state = Self::get_row_state(&model_song, &*self.model, current_song_id.as_ref());
- Self::set_row_state(&self.listbox, &model_song, &row, state);
-
- if state.is_playing && autoscroll && !in_viewport(row.upcast_ref()).unwrap_or(true) {
- self.animator
- .animate(20, move |p| vscroll_to(row.upcast_ref(), p).is_some());
+ fn update_list(&self) {
+ for (i, model_song) in self.list_model.iter().enumerate() {
+ let state = Self::get_item_state(&*self.model, &model_song);
+ model_song.set_state(state);
+ if state.is_playing
+ && self.model.autoscroll_to_playing()
+ && !self.model.is_selection_enabled()
+ {
+ self.autoscroll_to_playing(i);
}
}
}
- fn set_selection_active(listbox: >k::ListBox, active: bool) {
- let context = listbox.style_context();
+ fn set_selection_active(listview: >k::ListView, active: bool) {
+ let context = listview.style_context();
if active {
context.add_class("playlist--selectable");
- listbox.set_selection_mode(gtk::SelectionMode::Multiple);
} else {
context.remove_class("playlist--selectable");
- listbox.set_selection_mode(gtk::SelectionMode::None);
}
}
}
+impl SongModel {
+ fn set_state(&self, state: RowState) {
+ self.set_playing(state.is_playing);
+ self.set_selected(state.is_selected);
+ }
+}
+
impl EventListener for Playlist
where
Model: PlaylistModel + 'static,
@@ -225,14 +211,14 @@ where
} else {
match event {
AppEvent::SelectionEvent(SelectionEvent::SelectionChanged) => {
- self.update_list(false);
+ self.update_list();
}
AppEvent::PlaybackEvent(PlaybackEvent::TrackChanged(_)) => {
- self.update_list(true);
+ self.update_list();
}
AppEvent::SelectionEvent(SelectionEvent::SelectionModeChanged(_)) => {
- Self::set_selection_active(&self.listbox, self.model.is_selection_enabled());
- self.update_list(false);
+ Self::set_selection_active(&self.listview, self.model.is_selection_enabled());
+ self.update_list();
}
_ => {}
}
@@ -242,6 +228,6 @@ where
impl Component for Playlist {
fn get_root_widget(&self) -> >k::Widget {
- self.listbox.upcast_ref()
+ self.listview.upcast_ref()
}
}
diff --git a/src/app/components/playlist/song.css b/src/app/components/playlist/song.css
index c92727a0..9a8a11e6 100644
--- a/src/app/components/playlist/song.css
+++ b/src/app/components/playlist/song.css
@@ -19,12 +19,13 @@
opacity: 1;
}
-.playlist--selectable .song__index {
+.playlist--selectable .song__index, .playlist--selectable .song__icon {
opacity: 0;
}
-.playlist--selectable .song__checkbox {
+.playlist--selectable .song__checkbox, .playlist--selectable .song__checkbox check {
opacity: 1;
+ filter: none;
}
.song__title {
@@ -32,6 +33,7 @@
}
row {
+ transition: background-color 150ms ease;
margin: 1px 0;
}
@@ -49,7 +51,7 @@ row {
}
.song__menu {
- opacity: 0
+ opacity: 0;
}
.song__menu--enabled {
@@ -58,4 +60,18 @@ row {
row:hover .song__menu--enabled, .song__menu--enabled:checked {
opacity: 1;
+}
+
+.song check {
+ filter: opacity(1);
+}
+
+/* copied from adwaita */
+
+.playlist row:hover {
+ background-color: alpha(currentColor, 0.07);
+}
+
+.playlist row:active {
+ background-color: alpha(currentColor, 0.16);
}
\ No newline at end of file
diff --git a/src/app/components/playlist/song.rs b/src/app/components/playlist/song.rs
index c8666926..fafbf9d4 100644
--- a/src/app/components/playlist/song.rs
+++ b/src/app/components/playlist/song.rs
@@ -1,97 +1,135 @@
-use crate::app::components::{screen_add_css_provider, Component};
+use crate::app::components::screen_add_css_provider;
use crate::app::models::SongModel;
+
use gio::MenuModel;
-use gladis::Gladis;
use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::CompositeTemplate;
-#[derive(Gladis, Clone)]
-struct SongWidget {
- root: gtk::Widget,
- song_index: gtk::Label,
- song_icon: gtk::Image,
- song_checkbox: gtk::CheckButton,
- song_title: gtk::Label,
- song_artist: gtk::Label,
- song_length: gtk::Label,
- menu_btn: gtk::MenuButton,
-}
+mod imp {
+ use super::*;
-impl SongWidget {
- pub fn new() -> Self {
- screen_add_css_provider(resource!("/components/song.css"));
- Self::from_resource(resource!("/components/song.ui")).unwrap()
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/dev/alextren/Spot/components/song.ui")]
+ pub struct SongWidget {
+ #[template_child]
+ pub song_index: TemplateChild,
+
+ #[template_child]
+ pub song_icon: TemplateChild,
+
+ #[template_child]
+ pub song_checkbox: TemplateChild,
+
+ #[template_child]
+ pub song_title: TemplateChild,
+
+ #[template_child]
+ pub song_artist: TemplateChild,
+
+ #[template_child]
+ pub song_length: TemplateChild,
+
+ #[template_child]
+ pub menu_btn: TemplateChild,
}
- fn set_playing(widget: >k::Widget, is_playing: bool) {
- let song_class = "song--playing";
- let context = widget.style_context();
- if is_playing {
- context.add_class(song_class);
- } else {
- context.remove_class(song_class);
+ #[glib::object_subclass]
+ impl ObjectSubclass for SongWidget {
+ const NAME: &'static str = "SongWidget";
+ type Type = super::SongWidget;
+ type ParentType = gtk::Box;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
}
}
-}
-pub struct Song {
- widget: SongWidget,
-}
+ impl ObjectImpl for SongWidget {
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+ self.song_checkbox.set_sensitive(false);
+ }
+ }
-impl Song {
- pub fn new(model: SongModel) -> Self {
- let widget = SongWidget::new();
-
- model
- .bind_property("index", &widget.song_index, "label")
- .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
- .build();
-
- model
- .bind_property("title", &widget.song_title, "label")
- .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
- .build();
-
- model
- .bind_property("artist", &widget.song_artist, "label")
- .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
- .build();
- model
- .bind_property("duration", &widget.song_length, "label")
- .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
- .build();
-
- SongWidget::set_playing(&widget.root, model.get_playing());
-
- model.connect_playing_local(clone!(@weak widget.root as root => move |song| {
- SongWidget::set_playing(&root, song.get_playing());
- }));
+ impl WidgetImpl for SongWidget {}
+ impl BoxImpl for SongWidget {}
+}
- model.connect_selected_local(
- clone!(@weak widget.song_checkbox as checkbox => move |song| {
- checkbox.set_active(song.get_selected());
- }),
- );
+glib::wrapper! {
+ pub struct SongWidget(ObjectSubclass) @extends gtk::Widget, gtk::Box;
+}
- widget.song_checkbox.set_sensitive(false);
+impl SongWidget {
+ pub fn new() -> Self {
+ screen_add_css_provider(resource!("/components/song.css"));
+ glib::Object::new(&[]).expect("Failed to create an instance of SongWidget")
+ }
- Self { widget }
+ pub fn for_model(model: SongModel) -> Self {
+ let _self = Self::new();
+ _self.bind(&model);
+ _self
}
pub fn set_actions(&self, actions: Option<&gio::ActionGroup>) {
- self.get_root_widget().insert_action_group("song", actions);
+ self.insert_action_group("song", actions);
}
pub fn set_menu(&self, menu: Option<&MenuModel>) {
if menu.is_some() {
- let menu_btn = &self.widget.menu_btn;
- menu_btn.set_menu_model(menu);
- menu_btn.style_context().add_class("song__menu--enabled");
+ let widget = imp::SongWidget::from_instance(self);
+ widget.menu_btn.set_menu_model(menu);
+ widget
+ .menu_btn
+ .style_context()
+ .add_class("song__menu--enabled");
}
}
-}
-impl Component for Song {
- fn get_root_widget(&self) -> >k::Widget {
- &self.widget.root
+ fn set_playing(&self, is_playing: bool) {
+ let song_class = "song--playing";
+ let context = self.style_context();
+ if is_playing {
+ context.add_class(song_class);
+ } else {
+ context.remove_class(song_class);
+ }
+ }
+
+ fn set_selected(&self, is_selected: bool) {
+ imp::SongWidget::from_instance(self)
+ .song_checkbox
+ .set_active(is_selected);
+ let song_class = "song-selected";
+ let context = self.style_context();
+ if is_selected {
+ context.add_class(song_class);
+ } else {
+ context.remove_class(song_class);
+ }
+ }
+
+ pub fn bind(&self, model: &SongModel) {
+ let widget = imp::SongWidget::from_instance(self);
+
+ model.bind_index(&*widget.song_index, "label");
+ model.bind_title(&*widget.song_title, "label");
+ model.bind_artist(&*widget.song_artist, "label");
+ model.bind_duration(&*widget.song_length, "label");
+
+ self.set_playing(model.get_playing());
+ model.connect_playing_local(clone!(@weak self as _self => move |song| {
+ _self.set_playing(song.get_playing());
+ }));
+
+ self.set_selected(model.get_selected());
+ model.connect_selected_local(clone!(@weak self as _self => move |song| {
+ _self.set_selected(song.get_selected());
+ }));
}
}
diff --git a/src/app/components/playlist/song.ui b/src/app/components/playlist/song.ui
index a63a00cb..97f8bb6d 100644
--- a/src/app/components/playlist/song.ui
+++ b/src/app/components/playlist/song.ui
@@ -1,12 +1,8 @@
-
-