Bucketing now trusts the server release_type: drop the foreign-artist filter that hid band/guest releases (e.g. Vicky Psarakis' Albums were credited to Sicksense), render every non-empty bucket, hide 'Purchase Only' (download), and move 'Other' to the end, collapsed by default. Drop the misleading per-section item counts. New 'Latest release' highlight (last_release) and 'Appears On' track section (tracks_appears_on, previously mislabeled as Compilations). Per-section persisted 5-mode sort (artist_prefs) + per-section load-more capped at 4 pages, reusing get_releases_grid. New dedicated discography page (ArtistReleasesView + artist_releases controller) with sort + infinite scroll, reached via each section's 'See discography' link. Magazine/Stories sidebar tab (artist/story) with 1:1 thumbnails and a Network|Magazine tab switcher; hide the empty similar-artists block to remove the 'no similar' over populated-discovery incoherence. Header Share action. Per-release award ribbons. Fix custom artist AND album images: decode + apply immediately on upload and resolve the local override on navigation, so a custom image now shows for artists/albums with no Qobuz artwork. Data layer: type last_release as PageArtistRelease, add /artist/story endpoint + core/client methods + ArtistStory* models, export ArtistReleases* globals. cc
183 lines
8.7 KiB
Plaintext
183 lines
8.7 KiB
Plaintext
// QBZ Slint MVP — UI entry point.
|
|
//
|
|
// Stays thin: imports the feature screens and exports the single
|
|
// top-level window. No raw visual constants or domain logic here
|
|
// (MVP ADR, sections 13 and 21).
|
|
|
|
// Bundles + registers the Inter 18pt faces (compile-time embed) so the
|
|
// app can render with Inter — see the FONT_TEST_INTER toggle in main.rs.
|
|
import "assets/fonts/Inter_18pt-Regular.ttf";
|
|
import "assets/fonts/Inter_18pt-Medium.ttf";
|
|
import "assets/fonts/Inter_18pt-SemiBold.ttf";
|
|
import "assets/fonts/Inter_18pt-Bold.ttf";
|
|
|
|
import { Theme, ThemeColors } from "foundation/semantic-colors.slint";
|
|
import { ThemeState } from "foundation/theme-state.slint";
|
|
import { SplashScreen } from "splash/SplashScreen.slint";
|
|
import { LoginScreen } from "login/LoginScreen.slint";
|
|
import { AppShell } from "shell/AppShell.slint";
|
|
import { Toast } from "primitives/Toast.slint";
|
|
import { ShellState, MiniPlayerState } from "state.slint";
|
|
import { MiniShell } from "miniplayer/MiniShell.slint";
|
|
|
|
// Re-export the theming globals so the Rust layer can push palettes (Theme.c /
|
|
// Theme.is-system) and keep the dropdown index (ThemeState.mode) in sync.
|
|
export { Theme, ThemeColors } from "foundation/semantic-colors.slint";
|
|
export { ThemeState } from "foundation/theme-state.slint";
|
|
|
|
// Re-export the state globals so the Rust layer can populate them.
|
|
export { HomeState, HomeActions, DiscoverState, DiscoverActions, SectionDescriptor, ConfigRow, DiscoverBrowseState, DiscoverBrowseActions, ForYouState, MixState, GenreFilterState, GenreFilterActions, AlbumState, ArtistState, NavState, ShellState, SessionState, SettingsState, AlbumActions, ArtistActions, ArtworkActions, NowPlayingState, QueueState, LyricsState, LyricsLineItem, SearchState, SearchActions, NetworkSidebarState, NetworkSidebarActions, MusicianState, MusicianActions, LabelState, LabelActions, AwardState, AwardActions, AwardEntry, ArtistReleasesState, ArtistReleasesActions, LocationViewState, LocationViewActions, FavoritesState, FavoritesActions, PlaylistPickerState, PlaylistPickerActions, DuplicateConfirmState, DuplicateConfirmActions, PlaylistState, PlaylistActions, SidebarState, SidebarActions, SidebarFolderPopupState, CreatePlaylistState, CreatePlaylistActions, EditPlaylistState, EditPlaylistActions, CreateFolderState, CreateFolderActions, MyQbzCreateState, MyQbzCreateActions, DragState, DragActions, PlaylistManagerState, PlaylistManagerActions, OfflineManagerState, OfflineManagerActions, BlacklistState, BlacklistActions, BlacklistedArtistItem, MyQbzState, MyQbzActions, MixtapeCardItem, MyQbzAddState, MyQbzAddActions, MyQbzAddRow, MyQbzDetailState, MyQbzDetailActions, MixtapeDetailItem, MyQbzEditState, MyQbzEditActions, MyQbzMixState, MyQbzMixActions, DiscoBuilderState, DiscoBuilderActions, DiscoGroup, DiscoCandidate, LocalLibraryState, LocalLibraryActions, LibraryFoldersState, LibFolderEditState, LibraryManageActions, LibraryScanState, LibAlbumFilterState, LocalAlbumState, LocalAlbumActions, TagEditorState, TagEditorActions, FolderEditState, FolderEditActions, ToastState, TextUtil, QconnectDevState, QconnectDevice, CastState, CastDevice, CastActions, AppearanceState, MyQbzBrandingState, EphemeralPlayChoiceState, EphemeralPlayChoiceActions, PlexSettingsState, PlexAuthActions, PlexSectionItem, ScrobbleState, ScrobbleActions, OfflineState, OfflineModeActions, OfflineFavoritesState, OfflineFavoritesActions, ImportLogEntry, PlaylistImportState, PlaylistImportActions, DacWizardState, DacWizardActions, DacCandidateRow, RemediationRow, DacConfigRow, InfoCreditRow, InfoCreditPair, AlbumCreditPerformer, AlbumCreditTrack, TrackInfoState, TrackInfoActions, AlbumInfoState, AlbumInfoActions, SuggestionsState, SuggestionsActions, SuggestionCard, VisualizerState, ImmersiveState, ImmersiveSearchActions, ImmersiveActions, MiniPlayerState, WindowControlActions } from "state.slint";
|
|
|
|
// Which top-level screen is shown. The app starts on `splash` while it
|
|
// restores a saved session, then resolves to `shell` or `login`.
|
|
export enum AppScreen { splash, login, shell }
|
|
|
|
export component AppWindow inherits Window {
|
|
title: "QBZ";
|
|
preferred-width: 1180px;
|
|
preferred-height: 760px;
|
|
min-width: 940px;
|
|
min-height: 600px;
|
|
background: Theme.surface-main;
|
|
|
|
// Resolved by the Rust layer from the desktop environment (e.g. the
|
|
// KDE Plasma general font). Empty falls back to the Slint default.
|
|
in property <string> system-font: "";
|
|
default-font-family: root.system-font;
|
|
|
|
// Publish the live window width so content-area views can make
|
|
// responsiveness decisions (see ShellState.content-constrained).
|
|
init => {
|
|
ShellState.window-width = self.width;
|
|
}
|
|
changed width => {
|
|
ShellState.window-width = self.width;
|
|
}
|
|
|
|
in-out property <AppScreen> screen: AppScreen.splash;
|
|
|
|
// Forwarded to the Rust layer.
|
|
callback sign-in-via-browser();
|
|
callback use-system-browser();
|
|
callback start-offline();
|
|
callback open-tos();
|
|
callback open-album(string);
|
|
callback open-artist(string);
|
|
callback media-action(string, string, string);
|
|
callback header-menu-navigate(string);
|
|
callback discover-view-all(string, string);
|
|
callback settings-bool(string, bool);
|
|
callback settings-select(string, int);
|
|
callback settings-slider(string, int);
|
|
callback settings-reset();
|
|
callback settings-release-device();
|
|
callback logout();
|
|
callback close-app();
|
|
// D2 recovery banner: sign back in with the saved token.
|
|
callback recovery-login();
|
|
|
|
if root.screen == AppScreen.splash: SplashScreen { }
|
|
|
|
if root.screen == AppScreen.login: LoginScreen {
|
|
sign-in-via-browser => {
|
|
root.sign-in-via-browser();
|
|
}
|
|
use-system-browser => {
|
|
root.use-system-browser();
|
|
}
|
|
start-offline => {
|
|
root.start-offline();
|
|
}
|
|
open-tos => {
|
|
root.open-tos();
|
|
}
|
|
}
|
|
|
|
if root.screen == AppScreen.shell: AppShell {
|
|
// Fill the window. A conditionally-mounted child sizes to its PREFERRED
|
|
// size by default, so without this the shell sized to its content — fine
|
|
// while the right panels were always mounted (tall content), but once
|
|
// they became conditional, with both closed the shell collapsed to the
|
|
// top with dead space below. Bind to the real window size (`root` here is
|
|
// the AppWindow); `100%` did not pin it.
|
|
width: root.width;
|
|
height: root.height;
|
|
open-album(id) => {
|
|
root.open-album(id);
|
|
}
|
|
open-artist(id) => {
|
|
root.open-artist(id);
|
|
}
|
|
media-action(kind, id, action) => {
|
|
root.media-action(kind, id, action);
|
|
}
|
|
header-menu-navigate(route) => {
|
|
root.header-menu-navigate(route);
|
|
}
|
|
discover-view-all(e, t) => {
|
|
root.discover-view-all(e, t);
|
|
}
|
|
settings-bool(k, v) => {
|
|
root.settings-bool(k, v);
|
|
}
|
|
settings-select(k, i) => {
|
|
root.settings-select(k, i);
|
|
}
|
|
settings-slider(k, v) => {
|
|
root.settings-slider(k, v);
|
|
}
|
|
settings-reset => {
|
|
root.settings-reset();
|
|
}
|
|
settings-release-device => {
|
|
root.settings-release-device();
|
|
}
|
|
logout => {
|
|
root.logout();
|
|
}
|
|
close-app => {
|
|
root.close-app();
|
|
}
|
|
recovery-login => {
|
|
root.recovery-login();
|
|
}
|
|
}
|
|
|
|
// App-global in-app toast overlay. Declared last so it floats over
|
|
// every screen (splash / login / shell). Driven by ToastState via
|
|
// crate::toast.
|
|
Toast {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
}
|
|
|
|
// Second top-level window — the miniplayer. Borderless + transparent (set at
|
|
// the winit level in crate::miniplayer); the 6px transparent gutter + the
|
|
// rounded card recreate Tauri's floating-card look. The first QBZ surface with
|
|
// no system titlebar.
|
|
export component MiniPlayerWindow inherits Window {
|
|
title: "QBZ Mini Player";
|
|
min-width: 340px;
|
|
min-height: 57px;
|
|
background: transparent;
|
|
// BORDERLESS — the real fix. Slint's winit adapter OVERRIDES winit
|
|
// decorations at every window realization from this property
|
|
// (`set_decorations(!no_frame() || fullscreen)`, winitwindowadapter.rs:1268),
|
|
// so the BackendSelector attributes hook alone was being undone. `no-frame`
|
|
// is the native, authoritative borderless switch. First QBZ surface with no
|
|
// system titlebar; AppWindow keeps `no-frame: false` (default).
|
|
no-frame: true;
|
|
|
|
in property <string> system-font: "";
|
|
default-font-family: root.system-font;
|
|
|
|
// 6px transparent gutter around the rounded card.
|
|
MiniShell {
|
|
x: 6px;
|
|
y: 6px;
|
|
width: parent.width - 12px;
|
|
height: parent.height - 12px;
|
|
}
|
|
}
|