// Artist detail page. // // Mirrors the Svelte ArtistDetailView: back button, an artist header // (circular portrait, name, biography, action buttons), a Popular // Tracks list, and a Discography carousel. Data comes from the // ArtistState global; action buttons are visual for now. import { Theme } from "../foundation/semantic-colors.slint"; import { Typography } from "../foundation/typography.slint"; import { ArtistState, AlbumTrackItem, DiscoverSection, NavState, ContentView } from "../state.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint"; import { Carousel } from "../discover/Carousel.slint"; import { ScrollView } from "std-widgets.slint"; component CircleAction inherits Rectangle { in property icon; in property primary: false; in property diameter: 40px; callback clicked(); width: root.diameter; height: root.diameter; border-radius: root.diameter / 2; background: root.primary ? (ta.has-hover ? Theme.accent-hover : Theme.accent) : (ta.has-hover ? Theme.surface-hover : Theme.surface-elevated); QbzIcon { source: root.icon; width: 17px; height: 17px; x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; tint: root.primary ? Theme.accent-text : Theme.text-secondary; } ta := TouchArea { mouse-cursor: pointer; clicked => { root.clicked(); } } } component TrackRow inherits Rectangle { in property track; in property index; height: 52px; border-radius: 8px; background: ta.has-hover ? Theme.surface-hover : (Math.mod(root.index, 2) == 1 ? Theme.surface-card : transparent); HorizontalLayout { padding-left: 12px; padding-right: 12px; spacing: 16px; Text { text: root.track.number; width: 28px; color: Theme.text-muted; font-size: Typography.body; vertical-alignment: center; } VerticalLayout { alignment: center; horizontal-stretch: 1; Text { text: root.track.title; color: Theme.text-primary; font-size: Typography.body; font-weight: Typography.medium; overflow: elide; } } Text { text: root.track.duration; width: 70px; color: Theme.text-muted; font-size: Typography.body; horizontal-alignment: center; vertical-alignment: center; } Text { text: root.track.quality-tier == "hires" ? "Hi-Res" : (root.track.quality-tier == "cd" ? "CD" : ""); width: 70px; color: Theme.text-muted; font-size: Typography.legal; horizontal-alignment: center; vertical-alignment: center; } } ta := TouchArea { mouse-cursor: pointer; } } export component ArtistPageView inherits Rectangle { callback open-album(string); background: Theme.surface-main; ScrollView { horizontal-scrollbar-policy: ScrollBarPolicy.always-off; VerticalLayout { padding-left: 24px; padding-right: 24px; padding-top: 14px; padding-bottom: 100px; spacing: 0px; alignment: start; // --- Back button ------------------------------------------------- back := TouchArea { width: back-row.preferred-width; height: 28px; mouse-cursor: pointer; clicked => { NavState.view = ContentView.home; } back-row := HorizontalLayout { spacing: 6px; VerticalLayout { alignment: center; QbzIcon { source: @image-url("../assets/icons/arrow-left.svg"); width: 16px; height: 16px; tint: back.has-hover ? Theme.text-secondary : Theme.text-muted; } } Text { text: @tr("Back"); color: back.has-hover ? Theme.text-secondary : Theme.text-muted; font-size: Typography.body; vertical-alignment: center; } } } Rectangle { height: 22px; } // --- Artist header ----------------------------------------------- HorizontalLayout { spacing: 32px; alignment: start; Rectangle { width: 200px; height: 200px; border-radius: 100px; background: Theme.surface-elevated; drop-shadow-blur: 32px; drop-shadow-color: #00000080; drop-shadow-offset-y: 8px; clip: true; Image { width: 100%; height: 100%; source: ArtistState.artwork; image-fit: cover; } } VerticalLayout { alignment: start; padding-top: 8px; spacing: 0px; horizontal-stretch: 1; Text { text: ArtistState.name; color: Theme.text-primary; font-size: Typography.title; font-weight: Typography.bold; overflow: elide; } Rectangle { height: 12px; } Text { text: ArtistState.bio; visible: ArtistState.bio != ""; color: Theme.text-secondary; font-size: Typography.body; wrap: word-wrap; } Rectangle { height: 20px; } HorizontalLayout { alignment: start; spacing: 12px; CircleAction { icon: @image-url("../assets/icons/play.svg"); primary: true; diameter: 44px; } VerticalLayout { alignment: center; CircleAction { icon: @image-url("../assets/icons/shuffle.svg"); } } VerticalLayout { alignment: center; CircleAction { icon: @image-url("../assets/icons/heart.svg"); } } VerticalLayout { alignment: center; CircleAction { icon: @image-url("../assets/icons/cast.svg"); } } } } } Rectangle { height: 32px; } if ArtistState.loading && ArtistState.top-tracks.length == 0: Text { text: @tr("Loading artist..."); color: Theme.text-muted; font-size: Typography.body; } // --- Popular tracks ---------------------------------------------- if ArtistState.top-tracks.length > 0: Text { text: @tr("Popular Tracks"); color: Theme.text-primary; font-size: Typography.heading; font-weight: Typography.semibold; } if ArtistState.top-tracks.length > 0: Rectangle { height: 10px; } for track[index] in ArtistState.top-tracks: TrackRow { track: track; index: index; } // --- Discography ------------------------------------------------- if ArtistState.releases.length > 0: Rectangle { height: 28px; } if ArtistState.releases.length > 0: Carousel { section: { title: @tr("Discography"), albums: ArtistState.releases, }; album-clicked(id) => { root.open-album(id); } } } } }