mirror of
https://github.com/yattee/yattee.git
synced 2026-07-20 14:22:02 +00:00
Redesign macOS now-playing bar as centered Liquid Glass capsule
- Replace the full-width material overlay bar with a compact, centered capsule (Apple Music style) via a macOS-specific macOSCapsuleLayout - Extend glassBackground() to use real Liquid Glass on macOS 26 with a material fallback on macOS 15-25 (mirrors the PlayerOverlayButton pattern) - Remove the duplicate close button (close now comes only from the configurable button set) - Keep the capsule visible in the main window while the separate player window is open (window mode), still hidden in sheet mode
This commit is contained in:
@@ -119,14 +119,17 @@ struct ContentView: View {
|
||||
let playerState = appEnvironment.playerService.state
|
||||
let hasActiveVideo = playerState.currentVideo != nil
|
||||
let isExpanded = appEnvironment.navigationCoordinator.isPlayerExpanded
|
||||
// The expanded player is a separate window in window mode, so keep the capsule
|
||||
// visible alongside it. In sheet mode the sheet covers the window, so hide it.
|
||||
let usesWindow = appEnvironment.settingsManager.macPlayerMode.usesWindow
|
||||
|
||||
if hasActiveVideo && !isExpanded {
|
||||
if hasActiveVideo && (!isExpanded || usesWindow) {
|
||||
VStack(spacing: 0) {
|
||||
Spacer()
|
||||
MiniPlayerView()
|
||||
}
|
||||
// Add padding for tab bar
|
||||
.padding(.bottom, 49)
|
||||
// Float the capsule above the bottom edge (macOS uses a sidebar, not a tab bar)
|
||||
.padding(.bottom, 16)
|
||||
// Use move-only transition (no opacity) to prevent thumbnail flash during collapse
|
||||
.transition(.move(edge: .bottom))
|
||||
.animation(.spring(response: 0.3), value: hasActiveVideo)
|
||||
|
||||
@@ -64,23 +64,23 @@ struct GlassBackgroundModifier: ViewModifier {
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
#if os(iOS)
|
||||
if #available(iOS 26.0, *) {
|
||||
#if os(iOS) || os(macOS)
|
||||
if #available(iOS 26.0, macOS 26.0, *) {
|
||||
content.modifier(LiquidGlassModifier(style: style, shape: shape, colorScheme: colorScheme))
|
||||
} else {
|
||||
content.modifier(FallbackGlassModifier(shape: shape, material: fallbackMaterial, colorScheme: colorScheme))
|
||||
}
|
||||
#else
|
||||
// macOS / tvOS don't have glassEffect, always use fallback material
|
||||
// tvOS doesn't have glassEffect, always use fallback material
|
||||
content.modifier(FallbackGlassModifier(shape: shape, material: fallbackMaterial, colorScheme: colorScheme))
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - iOS 26+ Liquid Glass
|
||||
// MARK: - iOS 26+ / macOS 26+ Liquid Glass
|
||||
|
||||
#if os(iOS)
|
||||
@available(iOS 26.0, *)
|
||||
#if os(iOS) || os(macOS)
|
||||
@available(iOS 26.0, macOS 26.0, *)
|
||||
private struct LiquidGlassModifier: ViewModifier {
|
||||
let style: GlassStyle
|
||||
let shape: GlassShape
|
||||
|
||||
@@ -115,11 +115,15 @@ struct MiniPlayerView: View {
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
#if os(macOS)
|
||||
macOSCapsuleLayout
|
||||
#else
|
||||
if isTabAccessory {
|
||||
accessoryLayout
|
||||
} else {
|
||||
overlayLayout
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.accessibilityIdentifier("player.miniPlayer")
|
||||
.accessibilityLabel("player.miniPlayer")
|
||||
@@ -254,6 +258,64 @@ struct MiniPlayerView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - macOS Capsule Layout
|
||||
|
||||
#if os(macOS)
|
||||
/// Compact, centered capsule shown in the main window on macOS (Music.app style).
|
||||
/// The expanded player lives in a separate window, so this stays visible alongside it.
|
||||
private var macOSCapsuleLayout: some View {
|
||||
HStack(spacing: 10) {
|
||||
// Video preview - tap for PiP (or expand if PiP unavailable)
|
||||
videoPreviewView
|
||||
.frame(width: 44, height: 26)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
handleVideoPreviewTap()
|
||||
}
|
||||
|
||||
// Title/author area - tap to expand player (restores from PiP first if needed)
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
MarqueeText(
|
||||
text: displayTitle,
|
||||
font: .subheadline,
|
||||
fontWeight: .medium,
|
||||
foregroundStyle: .primary
|
||||
)
|
||||
|
||||
if let authorName = currentVideo?.author.name, !authorName.isEmpty {
|
||||
MarqueeText(
|
||||
text: authorName,
|
||||
font: .caption,
|
||||
foregroundStyle: .secondary
|
||||
)
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
expandPlayerWithPiPRestore()
|
||||
}
|
||||
|
||||
// Dynamic buttons from settings (includes close by default - no separate close button)
|
||||
buttonsView
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.frame(maxWidth: 420)
|
||||
.glassBackground(.regular, in: .capsule, fallback: .regularMaterial)
|
||||
.overlay(alignment: .bottom) {
|
||||
GeometryReader { geo in
|
||||
Rectangle()
|
||||
.fill(.red)
|
||||
.frame(width: geo.size.width * (playerState?.progress ?? 0), height: 2)
|
||||
}
|
||||
.frame(height: 2)
|
||||
}
|
||||
.clipShape(Capsule())
|
||||
.shadow(color: .black.opacity(0.25), radius: 8, y: 4)
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: - Shared Components
|
||||
|
||||
/// Video preview that shows live video when available, or thumbnail as fallback.
|
||||
|
||||
Reference in New Issue
Block a user