Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
9978093
Fix the incorrect detached status display 1
bo2themax Jul 31, 2025
92171b5
Fix the incorrect detached status display 2
bo2themax Jul 31, 2025
540f76b
Fix the incorrect detached status display 3
bo2themax Jul 31, 2025
6a23593
Fix the incorrect detached status display 4
bo2themax Jul 31, 2025
733429f
Add Try Liquid Glass Menu
bo2themax Jul 29, 2025
a30f9b0
try out new layout
bo2themax Jul 30, 2025
a622a3b
fix invalidation
bo2themax Jul 30, 2025
9d1fe9e
use enum
bo2themax Jul 30, 2025
10f6134
revert some
bo2themax Jul 30, 2025
4063343
WWDCWindowController as protocol
bo2themax Jul 30, 2025
a6449e7
revert some
bo2themax Jul 30, 2025
c8bcfb7
revert some
bo2themax Jul 30, 2025
d4a2d6f
update WWDCWindowControllerObject
bo2themax Jul 30, 2025
8dbbdc0
add WWDCCoordinator
bo2themax Jul 30, 2025
b4199eb
fix main actor warning
bo2themax Jul 30, 2025
21bd79d
use WWDCCoordinator
bo2themax Jul 30, 2025
c5c1387
use NewAppCoordinator
bo2themax Jul 30, 2025
c86c099
use associated TabController
bo2themax Jul 30, 2025
3a26373
revert some
bo2themax Jul 30, 2025
182d2c5
check sdk availability
bo2themax Jul 30, 2025
270944a
add background extension for thumbnail
bo2themax Jul 30, 2025
c71e1a1
use NewSessionsTableViewController
bo2themax Jul 30, 2025
8eaa467
full width table
bo2themax Jul 31, 2025
ff7383f
fill player and thumbnail
bo2themax Jul 31, 2025
d475968
use glass button for Play
bo2themax Jul 31, 2025
182c4cd
fix videos tab
bo2themax Jul 31, 2025
b0d468f
use filter item
bo2themax Jul 31, 2025
d4f112d
add pop over
bo2themax Jul 31, 2025
76c8df8
add new explore view
bo2themax Aug 1, 2025
4bbdb59
add new global search coordinator
bo2themax Aug 2, 2025
8ad2538
update list appearance
bo2themax Aug 2, 2025
697ad10
Use observable instead of passing View around
bo2themax Aug 2, 2025
c1a6fb2
Make TopicHeaderRowContent private
bo2themax Aug 2, 2025
9e1de21
rename & add animation for appearance
bo2themax Aug 3, 2025
cf2c3c3
disable movable by background
bo2themax Aug 3, 2025
abcc2f3
add filter header view
bo2themax Aug 3, 2025
90993d1
add clear menu
bo2themax Aug 3, 2025
3a4de44
hide and display filter item
bo2themax Aug 3, 2025
b373568
add search filter
bo2themax Aug 3, 2025
86883ff
improve search field interaction
bo2themax Aug 3, 2025
97b56a4
add new transcript view
bo2themax Aug 3, 2025
26e9089
find nearest line when replaying
bo2themax Aug 3, 2025
d7b5157
update transcript line style
bo2themax Aug 5, 2025
b4dff65
update filter header animation
bo2themax Aug 5, 2025
c6bd2e4
clean up toolbar items
bo2themax Aug 5, 2025
1657c8a
add search coordinator to detail view
bo2themax Aug 5, 2025
0b4379d
clean up warning
bo2themax Aug 5, 2025
460271a
ignore cyclomatic_complexity warning
bo2themax Aug 5, 2025
2399f67
remove chunky animation for now
bo2themax Aug 5, 2025
a7cf6e6
populate detail view
bo2themax Aug 5, 2025
8c172cc
update ui
bo2themax Aug 6, 2025
6190773
add actions view
bo2themax Aug 6, 2025
9a96fc9
fix related session interaction
bo2themax Aug 6, 2025
7bbe8ff
move folders
bo2themax Aug 6, 2025
69c35b4
switui contents
bo2themax Aug 7, 2025
4137aee
fix sidebar
bo2themax Aug 7, 2025
a9eb8a3
fix tab selecteion
bo2themax Aug 7, 2025
79f3872
change detail layout
bo2themax Aug 7, 2025
20d85d5
add new session actions
bo2themax Aug 7, 2025
988a406
update actions and transcript
bo2themax Aug 7, 2025
d6db43b
update transcript transition
bo2themax Aug 7, 2025
414ecef
update scale effect
bo2themax Aug 8, 2025
457fa42
support multiple selection
bo2themax Aug 8, 2025
d31717a
add context menus
bo2themax Aug 8, 2025
0ba76ea
move actions binding to SessionItemViewModel
bo2themax Aug 8, 2025
8097c8d
validate menu actions
bo2themax Aug 8, 2025
cccaa89
fix list focus
bo2themax Aug 8, 2025
9707c14
fix related session might not render correctly
bo2themax Aug 9, 2025
a56e6f6
unified SessionCoverView
bo2themax Aug 9, 2025
b6a78e7
use picker to switch tabs
bo2themax Aug 9, 2025
00f7e31
fix image memory spike
bo2themax Aug 9, 2025
744bcea
fix operation memory leak
bo2themax Aug 9, 2025
daf0d03
remove test code
bo2themax Aug 9, 2025
03af10c
use table as side bar
bo2themax Aug 9, 2025
f1da20f
optional thumbnailHeight
bo2themax Aug 10, 2025
4ed5e9e
reset observers when reuse
bo2themax Aug 10, 2025
60e39d3
update player ui
bo2themax Aug 10, 2025
6d76ebb
fix custom speed crash on macOS 26
bo2themax Aug 10, 2025
1cb1f3b
reset
bo2themax Aug 10, 2025
401041f
fix player glass
bo2themax Aug 10, 2025
50e7efb
update window loading
bo2themax Aug 11, 2025
59e77a0
player experience update
bo2themax Aug 11, 2025
29b0845
update color appearance preferences
bo2themax Aug 12, 2025
0bdf3ef
add glass floating view
bo2themax Aug 12, 2025
df5314e
update player
bo2themax Aug 13, 2025
d7d7790
clean up
bo2themax Aug 13, 2025
8aa8c30
fix entering pip in fullscreen
bo2themax Aug 13, 2025
debd1a9
move isplaying to viewmodel
bo2themax Aug 13, 2025
e116071
update video spinner
bo2themax Aug 13, 2025
7767fb3
adjust glass style
bo2themax Aug 13, 2025
cf4feaa
fix clip ui
bo2themax Aug 13, 2025
1a14faa
update loading indicator
bo2themax Aug 13, 2025
2015e24
update filter actions
bo2themax Aug 14, 2025
54e082a
delegate toolbar modifications
bo2themax Aug 14, 2025
85619f7
update search interaction
bo2themax Aug 14, 2025
c07ab2e
add section title
bo2themax Aug 16, 2025
a1b4403
add CanBePlayed observation
bo2themax Aug 16, 2025
7a7e099
move code with layout order
bo2themax Aug 16, 2025
1a54a5d
use share link
bo2themax Aug 17, 2025
e6b92be
revert a few
bo2themax Aug 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public struct DetachedPlaybackStatus: Identifiable {
var title: String
var subtitle: String
var snapshot: PUISnapshotClosure?

public var isFullScreen: Bool {
id == "fullScreen"
}
}

public extension DetachedPlaybackStatus {
Expand Down Expand Up @@ -65,6 +69,7 @@ public final class PUIDetachedPlaybackStatusViewController: NSViewController {
v.imageScaling = .scaleProportionallyUpOrDown
v.widthAnchor.constraint(equalToConstant: 74).isActive = true
v.heightAnchor.constraint(equalToConstant: 74).isActive = true
v.contentTintColor = .labelColor.preferred(in: .darkAqua)

return v
}()
Expand All @@ -73,7 +78,7 @@ public final class PUIDetachedPlaybackStatusViewController: NSViewController {
let f = NSTextField(labelWithString: "")

f.font = .systemFont(ofSize: 20, weight: .medium)
f.textColor = .labelColor
f.textColor = .labelColor.preferred(in: .darkAqua)
f.alignment = .center

return f
Expand All @@ -83,7 +88,7 @@ public final class PUIDetachedPlaybackStatusViewController: NSViewController {
let f = NSTextField(labelWithString: "")

f.font = .systemFont(ofSize: 16)
f.textColor = .secondaryLabelColor
f.textColor = .secondaryLabelColor.preferred(in: .darkAqua)
f.alignment = .center

return f
Expand Down
5 changes: 5 additions & 0 deletions PlayerUI/Definitions/Images.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ extension NSImage {
struct PUIControlMetrics: Hashable {
var symbolSize: CGFloat
var controlSize: CGFloat
var padding: CGFloat?
var glass: GlassStyle?
enum GlassStyle: Hashable {
case regular, clear
}

static let medium = PUIControlMetrics(symbolSize: 16, controlSize: 26)
static let large = PUIControlMetrics(symbolSize: 28, controlSize: 38)
Expand Down
6 changes: 6 additions & 0 deletions PlayerUI/Protocols/PUIPlayerViewDelegates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,11 @@ public protocol PUIPlayerViewAppearanceDelegate: AnyObject, PUIPlayerViewDetache
func playerViewShouldShowTimestampLabels(_ playerView: PUIPlayerView) -> Bool
func playerViewShouldShowFullScreenButton(_ playerView: PUIPlayerView) -> Bool
func playerViewBackAndForwardDuration(_ playerView: PUIPlayerView) -> BackForwardSkipDuration
func playerViewWillHidePlayControls(_ playerView: PUIPlayerView)
func playerViewWillShowPlayControls(_ playerView: PUIPlayerView)
}

public extension PUIPlayerViewAppearanceDelegate {
func playerViewWillHidePlayControls(_ playerView: PUIPlayerView) {}
func playerViewWillShowPlayControls(_ playerView: PUIPlayerView) {}
}
1 change: 1 addition & 0 deletions PlayerUI/Protocols/PUITimelineDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import Cocoa

@MainActor
public protocol PUITimelineDelegate: AnyObject {

func viewControllerForTimelineAnnotation(_ annotation: PUITimelineAnnotation) -> NSViewController?
Expand Down
4 changes: 2 additions & 2 deletions PlayerUI/Util/AVPlayer+Layout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public extension AVPlayer {
constraints = [
guide.widthAnchor.constraint(equalToConstant: videoRect.width),
guide.heightAnchor.constraint(equalToConstant: videoRect.height),
guide.centerYAnchor.constraint(equalTo: container.centerYAnchor),
guide.centerXAnchor.constraint(equalTo: container.centerXAnchor)
guide.centerYAnchor.constraint(equalTo: container.safeAreaLayoutGuide.centerYAnchor),
guide.centerXAnchor.constraint(equalTo: container.safeAreaLayoutGuide.centerXAnchor)
]

NSLayoutConstraint.activate(constraints)
Expand Down
26 changes: 26 additions & 0 deletions PlayerUI/Util/NSColor+AppearanceCustomization.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// NSColor+AppearanceCustomization.swift
// WWDC
//
// Created by luca on 12.08.2025.
// Copyright © 2025 Guilherme Rambo. All rights reserved.
//

import AppKit

public extension NSColor {
func preferred(in appearanceName: NSAppearance.Name) -> NSColor {
return NSColor(cgColor: preferredCGColor(in: appearanceName)) ?? self
}

func preferredCGColor(in appearanceName: NSAppearance.Name) -> CGColor {
guard let appearance = NSAppearance(named: appearanceName) else {
return cgColor
}
var result = cgColor
appearance.performAsCurrentDrawingAppearance { // accessing cgcolor will get the correct color under specific appearance
result = cgColor
}
return result
}
}
35 changes: 35 additions & 0 deletions PlayerUI/Util/NSView+BackgroundExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// NSView+BackgroundExtension.swift
// WWDC
//
// Created by luca on 11.08.2025.
// Copyright © 2025 Guilherme Rambo. All rights reserved.
//

import AppKit
import SwiftUI

public extension NSView {
func backgroundExtensionEffect(reflect edges: Edge.Set = .all, isEnabled: Bool = true) -> NSView {
if isEnabled, #available(macOS 26.0, *) {
let extensionView = NSBackgroundExtensionView()
extensionView.contentView = self
extensionView.automaticallyPlacesContentView = false
extensionView.translatesAutoresizingMaskIntoConstraints = false
// only enable reflection effect on leading edge
let targetLeadingAnchor = edges.contains(.leading) ? extensionView.safeAreaLayoutGuide.leadingAnchor : extensionView.leadingAnchor
let targetTopAnchor = edges.contains(.top) ? extensionView.safeAreaLayoutGuide.topAnchor : extensionView.topAnchor
let targetTrailingAnchor = edges.contains(.trailing) ? extensionView.safeAreaLayoutGuide.trailingAnchor : extensionView.trailingAnchor
let targetBottomAnchor = edges.contains(.bottom) ? extensionView.safeAreaLayoutGuide.bottomAnchor : extensionView.bottomAnchor
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: targetTopAnchor),
leadingAnchor.constraint(equalTo: targetLeadingAnchor),
bottomAnchor.constraint(equalTo: targetBottomAnchor),
trailingAnchor.constraint(equalTo: targetTrailingAnchor)
])
return extensionView
} else {
return self
}
}
}
195 changes: 195 additions & 0 deletions PlayerUI/Util/NSView+Glass.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
//
// NSView+Glass.swift
// WWDC
//
// Created by luca on 12.08.2025.
// Copyright © 2025 Guilherme Rambo. All rights reserved.
//

import AppKit
import SwiftUI

public extension NSView {
@available(macOS 26.0, *)
func glassEffect(style: NSGlassEffectView.Style? = nil, cornerRadius: CGFloat? = nil, tintColor: NSColor? = nil) -> NSView {
let effectView = NSGlassEffectView()
if let cornerRadius {
effectView.cornerRadius = cornerRadius
}
if let style {
effectView.style = style
}
effectView.contentView = self
effectView.tintColor = tintColor
return effectView
}

// will be bridged to swiftui and back
@available(macOS 26.0, *)
func glassCapsuleEffect(_ glass: Glass = .regular, background: Color? = nil) -> NSView {
NSHostingView(rootView: ConditionalGlassViewWrapper(subview: self, glass: glass, background: background, padding: nil, shape: .capsule))
}

// will be bridged to swiftui and back
@available(macOS 26.0, *)
func glassCircleEffect(_ glass: Glass = .regular, background: Color? = nil, padding: CGFloat? = nil) -> NSView {
NSHostingView(rootView: ConditionalGlassViewWrapper(subview: self, glass: glass, background: background, padding: padding, shape: .circle))
}

@available(macOS 26.0, *)
static func horizontalGlassContainer(_ glass: Glass = .regular, background: Color? = nil, paddingEdge: Edge.Set = .all, padding: CGFloat? = nil, containerSpacing: CGFloat? = nil, spacing: CGFloat? = nil, groups: [[NSView]]) -> NSView {
NSHostingView(rootView: GroupedHorizontalGlassContainer(axis: .horizontal, subviewGroups: groups, containerSpacing: containerSpacing, spacing: spacing, glass: glass, background: background, paddingEdge: paddingEdge, padding: padding, shape: .capsule))
}

@available(macOS 26.0, *)
static func verticalGlassContainer(_ glass: Glass = .regular, background: Color? = nil, paddingEdge: Edge.Set = .all, padding: CGFloat? = nil, containerSpacing: CGFloat? = nil, spacing: CGFloat? = nil, groups: [[NSView]]) -> NSView {
NSHostingView(rootView: GroupedHorizontalGlassContainer(axis: .vertical, subviewGroups: groups, containerSpacing: containerSpacing, spacing: spacing, glass: glass, background: background, paddingEdge: paddingEdge, padding: padding, shape: .capsule))
}
}

@available(macOS 26.0, *)
private struct GroupedHorizontalGlassContainer<S: Shape>: View {
let axis: Axis.Set
let subviewGroups: [[NSView]]
let containerSpacing: CGFloat?
let spacing: CGFloat?
let glass: Glass
let background: Color?
let paddingEdge: Edge.Set
let padding: CGFloat?
let shape: S
@Namespace private var namespace
var body: some View {
GlassEffectContainer(spacing: containerSpacing ?? spacing) {
Stack(axis: axis, spacing: spacing) {
ForEach(subviewGroups.indices, id: \.self) { groupIdx in
ConditionalHorizontalGlassViewWrapper(
axis: axis,
subviews: subviewGroups[groupIdx],
spacing: spacing,
glass: glass,
tint: background,
paddingEdge: paddingEdge,
padding: padding,
id: "\(groupIdx)",
namespace: namespace,
shape: shape
)
}
}
}
}

@available(macOS 26.0, *)
private struct ConditionalHorizontalGlassViewWrapper: View {
let axis: Axis.Set
let subviews: [NSView]
let spacing: CGFloat?
let glass: Glass
let background: Color?
let paddingEdge: Edge.Set
let padding: CGFloat?
let id: String
let namespace: Namespace.ID
let shape: S
@State private var isSubviewsHidden: [Bool]
init(axis: Axis.Set, subviews: [NSView], spacing: CGFloat?, glass: Glass, tint: Color?, paddingEdge: Edge.Set, padding: CGFloat?, id: String, namespace: Namespace.ID, shape: S) {
self.axis = axis
self.subviews = subviews
self.spacing = spacing
self.glass = glass
self.background = tint
self.paddingEdge = paddingEdge
self.padding = padding
self.isSubviewsHidden = subviews.map(\.isHidden)
self.id = id
self.namespace = namespace
self.shape = shape
}

var body: some View {
Stack(axis: axis, spacing: spacing) {
ForEach(subviews.indices, id: \.self) { idx in
ConditionalViewWrapper(subview: subviews[idx], isHidden: $isSubviewsHidden[idx])
}
}
.padding(paddingEdge, padding)
.background(isSubviewsHidden.allSatisfy({ $0 }) ? .clear : background)
.clipShape(shape)
.glassEffect(isSubviewsHidden.allSatisfy({ $0 }) ? .identity : glass, in: .capsule)
.glassEffectID(id, in: namespace)
.opacity(isSubviewsHidden.allSatisfy({ $0 }) ? 0 : 1)
}
}
}

extension View {
@ViewBuilder
func Stack<Content: View>(axis: Axis.Set, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content) -> some View {
if axis.contains(.vertical) {
VStack(spacing: spacing, content: content)
} else {
HStack(spacing: spacing, content: content)
}
}
}

@available(macOS 26.0, *)
private struct ConditionalViewWrapper: View {
let subview: NSView
@State private var isSubviewHidden: Bool = false
var isHidden: Binding<Bool>?
var body: some View {
Group {
if !isSubviewHidden {
ViewWrapper(view: subview)
.help(subview.toolTip ?? "")
}
}
.onReceive(subview.publisher(for: \.isHidden).removeDuplicates()) { newValue in
withAnimation {
isSubviewHidden = newValue
isHidden?.wrappedValue = newValue
}
}
}
}

@available(macOS 26.0, *)
private struct ConditionalGlassViewWrapper<S: Shape>: View {
let subview: NSView
let glass: Glass
let background: Color?
let padding: CGFloat?
let shape: S
@State private var isSubviewHidden: Bool = false
var isHidden: Binding<Bool>?
var body: some View {
Group {
if !isSubviewHidden {
ViewWrapper(view: subview)
.padding(.all, padding)
.background(background)
.clipShape(shape)
.glassEffect(glass, in: shape)
.help(subview.toolTip ?? "")
.transition(.blurReplace)
}
}
.onReceive(subview.publisher(for: \.isHidden).removeDuplicates()) { newValue in
withAnimation {
isSubviewHidden = newValue
isHidden?.wrappedValue = newValue
}
}
}
}

private struct ViewWrapper: NSViewRepresentable {
let view: NSView
func makeNSView(context: Context) -> NSView {
view
}

func updateNSView(_ nsView: NSView, context: Context) {}
}
Loading