Yattee v2 rewrite

This commit is contained in:
Arkadiusz Fal
2026-02-08 18:31:16 +01:00
parent 20d0cfc0c7
commit 05f921d605
1043 changed files with 163875 additions and 68430 deletions

View File

@@ -0,0 +1,92 @@
//
// ChannelAvatarView.swift
// Yattee
//
// Channel avatar that supports loading from Yattee Server.
// When the author doesn't have a thumbnail URL (common with yt-dlp),
// this view will attempt to load the avatar from the server's avatar endpoint.
//
import SwiftUI
import NukeUI
// MARK: - Channel Avatar View
/// A channel avatar view that supports loading from Yattee Server.
struct ChannelAvatarView: View {
let author: Author
let size: CGFloat
let yatteeServerURL: URL?
let source: ContentSource?
init(author: Author, size: CGFloat = 40, yatteeServerURL: URL? = nil, source: ContentSource? = nil) {
self.author = author
self.size = size
self.yatteeServerURL = yatteeServerURL
self.source = source
}
var body: some View {
LazyImage(url: avatarURL) { state in
ZStack {
// Background circle with letter placeholder
Circle()
.fill(.quaternary)
.overlay {
if state.image == nil {
Text(String(author.name.prefix(1)))
.font(size > 50 ? .title : .subheadline)
.fontWeight(.medium)
.foregroundStyle(.secondary)
}
}
// Avatar image
if let image = state.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
}
}
}
.frame(width: size, height: size)
.clipShape(Circle())
// Isolate geometry to prevent position jumping during parent animations
.geometryGroup()
}
/// Constructs the avatar URL from author's thumbnail or Yattee Server.
private var avatarURL: URL? {
// Primary: Use author's thumbnail URL if available
if let authorURL = author.thumbnailURL {
return authorURL
}
guard let serverURL = yatteeServerURL, !author.id.isEmpty else {
return nil
}
// Only use Yattee Server fallback for YouTube sources
// Non-YouTube sources (extracted, federated) cannot use the avatar endpoint
if let source = source {
switch source {
case .global(let provider):
guard provider == ContentSource.youtubeProvider else {
return nil
}
case .federated, .extracted:
return nil
}
}
// Construct URL from Yattee Server - server handles fetching/caching
let avatarSize = Int(size * 2) // 2x for retina
let roundedSize = [32, 48, 76, 100, 176, 512].min { abs($0 - avatarSize) < abs($1 - avatarSize) } ?? 176
return serverURL
.appendingPathComponent("api/v1/channels")
.appendingPathComponent(author.id)
.appendingPathComponent("avatar")
.appendingPathComponent("\(roundedSize).jpg")
}
}