mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-04 16:57:00 +01:00
feat(app,android,ios): display type, artwork fade-in, shimmer, lifting tiles and paged shelves
A first pass at making the three clients look like a streaming app rather than a file browser, with no new data behind it. - Type: a Hero rung (34) set in Inter Display Bold, the optical size Inter draws for large text, for the banner's name and a title page's name where no logo stands in. Desktop `Role::Hero` and `theme::display`; Android and iOS `displayLarge`, at a tighter 1.1 leading. The font has its private use mappings stripped like the other two cuts. - Artwork fades in over its skeleton on Android and iOS, as it already did on desktop cards; art already decoded draws at once. Desktop episode stills and logos fade in too. - Skeletons shimmer: a soft diagonal highlight instead of a pulse. - Desktop tiles lift towards the viewer under the pointer, capped so a lifted tile never reaches its neighbour, and keyboard focus gets a ring of its own. - Desktop shelves share one `ui::shelf`: edges fade into the page and, under the pointer, round buttons page the row along. - Genres are chips on Android and iOS; quality (4K, HDR, 10-bit, Dual audio) moves out of the facts line into small outlined marks on all three. - Desktop title page and hero buttons use a tonal style beside the accent one, and the player's top bar a dark glass style, instead of egui's stock buttons. Episode numbers leave monospace for the type ramp. - The phone players' top and bottom bars are gradients rather than flat bands, and a double-tap seek is felt as well as seen.
This commit is contained in:
1 parent
3ebfd1a57b
commit
255e463fc8
33 files changed
+886
-290
No files matched your search
@@ -150,10 +150,10 @@ struct TitleView: View {
|
||||
// A light page keeps the name: a clear logo is lettering made for
|
||||
// dark art, white as often as not.
|
||||
if scheme.light {
|
||||
Text(title.displayName).textStyle(.headlineMedium).foregroundStyle(scheme.onSurface)
|
||||
Text(title.displayName).textStyle(.displayLarge).foregroundStyle(scheme.onSurface)
|
||||
} else {
|
||||
RemoteLogo(title.logoUrl, title.displayName, maxWidth: 280, maxHeight: 88) {
|
||||
Text(title.displayName).textStyle(.headlineMedium).foregroundStyle(scheme.onSurface)
|
||||
Text(title.displayName).textStyle(.displayLarge).foregroundStyle(scheme.onSurface)
|
||||
}
|
||||
.padding(.bottom, 4)
|
||||
}
|
||||
@@ -179,6 +179,13 @@ struct TitleView: View {
|
||||
if !facts.isEmpty {
|
||||
Text(facts).textStyle(.bodySmall).foregroundStyle(scheme.onSurfaceVariant).padding(.top, 2)
|
||||
}
|
||||
// What the files are, rather than what the title is.
|
||||
if !title.quality.isEmpty {
|
||||
HStack(spacing: 4) {
|
||||
ForEach(title.quality, id: \.self) { SpecBadge(text: $0) }
|
||||
}
|
||||
.padding(.top, 8)
|
||||
}
|
||||
// The one thing the page is for, full width and naming what it will
|
||||
// play: "Resume" alone left the viewer guessing which episode.
|
||||
Button { onPlay(playlist, nextUp) } label: {
|
||||
@@ -213,9 +220,17 @@ struct TitleView: View {
|
||||
.padding(.top, 12)
|
||||
.onTapGesture { overviewOpen.toggle() }
|
||||
}
|
||||
if !title.genres.isEmpty || !title.tags.isEmpty {
|
||||
Text((title.genres + title.tags.prefix(4)).joined(separator: " · "))
|
||||
.textStyle(.labelMedium).foregroundStyle(scheme.onSurfaceVariant).padding(.top, 10)
|
||||
// Genres as chips, then the provider's tags quieter after them: a
|
||||
// tag is its community describing it, not what it is filed as.
|
||||
if !title.genres.isEmpty {
|
||||
FlowLayout(spacing: 6) {
|
||||
ForEach(title.genres, id: \.self) { GenreChip(text: $0) }
|
||||
}
|
||||
.padding(.top, 12)
|
||||
}
|
||||
if !title.tags.isEmpty {
|
||||
Text(title.tags.prefix(4).joined(separator: " · "))
|
||||
.textStyle(.labelMedium).foregroundStyle(scheme.onSurfaceVariant).padding(.top, 8)
|
||||
}
|
||||
// Said plainly, because it changes what a re-match will do.
|
||||
if title.manualMatch {
|
||||
@@ -231,7 +246,7 @@ struct TitleView: View {
|
||||
// A series' running time is its episodes', which each row says.
|
||||
let runtime: String? = title.kind == .film ? title.runtime.map(formatRuntime) : nil
|
||||
let parts: [String?] = [title.formatLabel, title.seasonLabel ?? year, runtime, rating, watched]
|
||||
return (parts.compactMap { $0 } + title.quality).joined(separator: " · ")
|
||||
return parts.compactMap { $0 }.joined(separator: " · ")
|
||||
}
|
||||
|
||||
private func playLabel(playlist: [EpisodeRecord], upNext: EpisodeRecord?) -> String {
|
||||
|
||||
Reference in new issue
Block a user