mirror of
https://github.com/yattee/yattee.git
synced 2026-05-12 18:35:05 +00:00
Improve macOS channel toolbar header
This commit is contained in:
@@ -284,6 +284,11 @@ struct ChannelView: View {
|
|||||||
isPlayerExpanded: appEnvironment?.navigationCoordinator.isPlayerExpanded ?? false
|
isPlayerExpanded: appEnvironment?.navigationCoordinator.isPlayerExpanded ?? false
|
||||||
))
|
))
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
#if os(macOS)
|
||||||
|
ToolbarItem(placement: .principal) {
|
||||||
|
collapsedToolbarTitle(name: channel.name, thumbnailURL: channel.thumbnailURL)
|
||||||
|
}
|
||||||
|
#else
|
||||||
ToolbarItem(placement: .principal) {
|
ToolbarItem(placement: .principal) {
|
||||||
// Collapsed title in toolbar
|
// Collapsed title in toolbar
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
@@ -308,6 +313,7 @@ struct ChannelView: View {
|
|||||||
}
|
}
|
||||||
.opacity(collapsedTitleOpacity)
|
.opacity(collapsedTitleOpacity)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: .primaryAction) {
|
||||||
Button {
|
Button {
|
||||||
@@ -437,6 +443,11 @@ struct ChannelView: View {
|
|||||||
.background(viewBackgroundColor)
|
.background(viewBackgroundColor)
|
||||||
.ignoresSafeArea(edges: .top)
|
.ignoresSafeArea(edges: .top)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
#if os(macOS)
|
||||||
|
ToolbarItem(placement: .principal) {
|
||||||
|
collapsedToolbarTitle(name: cached.name, thumbnailURL: cached.thumbnailURL)
|
||||||
|
}
|
||||||
|
#else
|
||||||
ToolbarItem(placement: .principal) {
|
ToolbarItem(placement: .principal) {
|
||||||
// Collapsed title in toolbar (using cached data)
|
// Collapsed title in toolbar (using cached data)
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
@@ -461,6 +472,7 @@ struct ChannelView: View {
|
|||||||
}
|
}
|
||||||
.opacity(collapsedTitleOpacity)
|
.opacity(collapsedTitleOpacity)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: .primaryAction) {
|
||||||
Button {
|
Button {
|
||||||
@@ -806,6 +818,30 @@ struct ChannelView: View {
|
|||||||
|
|
||||||
// MARK: - Header
|
// MARK: - Header
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
private func collapsedToolbarTitle(name: String, thumbnailURL: URL?) -> some View {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
LazyImage(url: thumbnailURL) { state in
|
||||||
|
if let image = state.image {
|
||||||
|
image
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
} else {
|
||||||
|
Circle()
|
||||||
|
.fill(.quaternary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: 28, height: 28)
|
||||||
|
.clipShape(Circle())
|
||||||
|
|
||||||
|
Text(name)
|
||||||
|
.font(.headline)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private func header(_ channel: Channel) -> some View {
|
private func header(_ channel: Channel) -> some View {
|
||||||
header(name: channel.name, thumbnailURL: channel.thumbnailURL, bannerURL: channel.bannerURL)
|
header(name: channel.name, thumbnailURL: channel.thumbnailURL, bannerURL: channel.bannerURL)
|
||||||
}
|
}
|
||||||
@@ -814,11 +850,17 @@ struct ChannelView: View {
|
|||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
// Calculate avatar vertical position
|
// Calculate avatar vertical position
|
||||||
let avatarBottomPadding: CGFloat = 20
|
let avatarBottomPadding: CGFloat = 20
|
||||||
|
#if os(macOS)
|
||||||
|
let headerAvatarSize = avatarSize
|
||||||
|
let nameSpace: CGFloat = 0
|
||||||
|
#else
|
||||||
// Smoothly interpolate the space for channel name (40pt when visible, 0 when collapsed)
|
// Smoothly interpolate the space for channel name (40pt when visible, 0 when collapsed)
|
||||||
// Name starts fading at progress 0, fully gone at 0.3
|
// Name starts fading at progress 0, fully gone at 0.3
|
||||||
let nameSpaceProgress = min(collapseProgress / 0.3, 1.0)
|
let nameSpaceProgress = min(collapseProgress / 0.3, 1.0)
|
||||||
let nameSpace: CGFloat = 40 * (1 - nameSpaceProgress)
|
let nameSpace: CGFloat = 40 * (1 - nameSpaceProgress)
|
||||||
let avatarContentHeight: CGFloat = currentAvatarSize + nameSpace + avatarBottomPadding
|
let headerAvatarSize = currentAvatarSize
|
||||||
|
#endif
|
||||||
|
let avatarContentHeight: CGFloat = headerAvatarSize + nameSpace + avatarBottomPadding
|
||||||
let idealAvatarY = headerHeight - avatarContentHeight
|
let idealAvatarY = headerHeight - avatarContentHeight
|
||||||
// Minimum Y position to prevent going into nav bar
|
// Minimum Y position to prevent going into nav bar
|
||||||
// On iOS 18+ iPhone, the search bar is present in the navigation area (~56pt + padding)
|
// On iOS 18+ iPhone, the search bar is present in the navigation area (~56pt + padding)
|
||||||
@@ -860,10 +902,14 @@ struct ChannelView: View {
|
|||||||
.offset(y: pinOffset)
|
.offset(y: pinOffset)
|
||||||
|
|
||||||
// Avatar and channel name - pinned to bottom of banner
|
// Avatar and channel name - pinned to bottom of banner
|
||||||
|
#if os(macOS)
|
||||||
|
let avatarOpacity: CGFloat = 1
|
||||||
|
#else
|
||||||
// Smooth opacity fade based on collapse progress
|
// Smooth opacity fade based on collapse progress
|
||||||
let avatarOpacity = max(0, 1.0 - collapseProgress * 1.4)
|
let avatarOpacity = max(0, 1.0 - collapseProgress * 1.4)
|
||||||
// Channel name fades out faster than avatar (starts fading at 0, gone by 0.3)
|
// Channel name fades out faster than avatar (starts fading at 0, gone by 0.3)
|
||||||
let nameOpacity = max(0, 1.0 - collapseProgress * 3.3)
|
let nameOpacity = max(0, 1.0 - collapseProgress * 3.3)
|
||||||
|
#endif
|
||||||
|
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
LazyImage(url: thumbnailURL) { state in
|
LazyImage(url: thumbnailURL) { state in
|
||||||
@@ -876,12 +922,12 @@ struct ChannelView: View {
|
|||||||
.fill(.ultraThinMaterial)
|
.fill(.ultraThinMaterial)
|
||||||
.overlay {
|
.overlay {
|
||||||
Text(String(name.prefix(1)))
|
Text(String(name.prefix(1)))
|
||||||
.font(.system(size: currentAvatarSize * 0.4, weight: .semibold))
|
.font(.system(size: headerAvatarSize * 0.4, weight: .semibold))
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: currentAvatarSize, height: currentAvatarSize)
|
.frame(width: headerAvatarSize, height: headerAvatarSize)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.overlay(
|
.overlay(
|
||||||
Circle()
|
Circle()
|
||||||
@@ -889,6 +935,7 @@ struct ChannelView: View {
|
|||||||
)
|
)
|
||||||
.shadow(color: .black.opacity(0.4), radius: 10, y: 5)
|
.shadow(color: .black.opacity(0.4), radius: 10, y: 5)
|
||||||
|
|
||||||
|
#if !os(macOS)
|
||||||
// Channel name with smooth fade
|
// Channel name with smooth fade
|
||||||
Text(name)
|
Text(name)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
@@ -896,6 +943,7 @@ struct ChannelView: View {
|
|||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.shadow(color: .black.opacity(0.6), radius: 4, y: 2)
|
.shadow(color: .black.opacity(0.6), radius: 4, y: 2)
|
||||||
.opacity(nameOpacity)
|
.opacity(nameOpacity)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.offset(y: clampedAvatarY + pinOffset)
|
.offset(y: clampedAvatarY + pinOffset)
|
||||||
.opacity(avatarOpacity)
|
.opacity(avatarOpacity)
|
||||||
@@ -2382,6 +2430,9 @@ private struct ChannelScrollOffsetModifier: ViewModifier {
|
|||||||
var isPlayerExpanded: Bool
|
var isPlayerExpanded: Bool
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
|
#if os(macOS)
|
||||||
|
content
|
||||||
|
#else
|
||||||
content
|
content
|
||||||
.onScrollGeometryChange(for: CGFloat.self) { geometry in
|
.onScrollGeometryChange(for: CGFloat.self) { geometry in
|
||||||
geometry.contentOffset.y
|
geometry.contentOffset.y
|
||||||
@@ -2393,6 +2444,7 @@ private struct ChannelScrollOffsetModifier: ViewModifier {
|
|||||||
scrollOffset = newValue
|
scrollOffset = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user