Files
yattee/Yattee/Views/Components/BookmarkRowView.swift
Arkadiusz Fal b47888fe04 Add custom accent color with system color picker
Add a Custom swatch to the appearance settings accent color grid,
backed by a hex value stored in settings (synced via iCloud like the
rest). iOS uses the native ColorPicker wheel; macOS uses a circular
swatch that opens NSColorPanel, since the SwiftUI color well looks out
of place among the circles. All accent consumers now read the resolved
color through SettingsManager.resolvedAccentColor.

The indigo preset is retired from the grid but kept in the enum, so
users who selected it keep their color until they pick another one.

Claude-Session: https://claude.ai/code/session_0154KH8RAVAvm6iVanhmoW8o
2026-07-04 23:57:59 +02:00

140 lines
3.8 KiB
Swift

//
// BookmarkRowView.swift
// Yattee
//
// Row view for displaying bookmarked videos with tags and notes.
//
import SwiftData
import SwiftUI
/// Row view for displaying bookmarked videos.
/// Uses VideoRowView for consistent presentation with additional tags/notes display.
/// Automatically handles DeArrow integration.
/// Supports optional queue context for auto-play functionality.
struct BookmarkRowView: View {
@Environment(\.appEnvironment) private var appEnvironment
let bookmark: Bookmark
var style: VideoRowStyle = .regular
var watchProgress: Double? = nil
let onRemove: () -> Void
// Queue context (optional, enables auto-play when provided)
var queueSource: QueueSource? = nil
var sourceLabel: String? = nil
var videoList: [Video]? = nil
var videoIndex: Int? = nil
var loadMoreVideos: LoadMoreVideosCallback? = nil
private var accentColor: Color {
appEnvironment?.settingsManager.resolvedAccentColor ?? .accentColor
}
private var isBookmarkValid: Bool {
bookmark.modelContext != nil && !bookmark.isDeleted
}
private var video: Video {
bookmark.toVideo()
}
private var hasTags: Bool {
guard isBookmarkValid else { return false }
return !bookmark.tags.isEmpty
}
private var hasNote: Bool {
guard isBookmarkValid else { return false }
if let note = bookmark.note, !note.isEmpty {
return true
}
return false
}
private var hasMetadata: Bool {
hasTags || hasNote
}
/// Leading padding to align tags/notes with the text content (past thumbnail)
private var metadataLeadingPadding: CGFloat {
let hstackSpacing: CGFloat = 12
return style.thumbnailWidth + hstackSpacing
}
var body: some View {
if isBookmarkValid {
content
}
}
private var content: some View {
VStack(alignment: .leading, spacing: 0) {
// Video row content
videoRowContent
// Tags and notes (text-aligned, past thumbnail) - one line
if hasMetadata && style != .compact {
bookmarkMetadataLine
.padding(.leading, metadataLeadingPadding)
.padding(.top, 4)
}
}
.tappableVideo(
video,
queueSource: queueSource,
sourceLabel: sourceLabel,
videoList: videoList,
videoIndex: videoIndex,
loadMoreVideos: loadMoreVideos
)
.videoContextMenu(
video: video,
customActions: [
VideoContextAction(
String(localized: "home.bookmarks.remove"),
systemImage: "trash",
role: .destructive,
action: onRemove
)
],
context: .bookmarks
)
}
@ViewBuilder
private var videoRowContent: some View {
VideoRowView(
video: video,
style: style,
watchProgress: watchProgress
)
}
/// Single line with tags and note separated by dot
@ViewBuilder
private var bookmarkMetadataLine: some View {
HStack(spacing: 4) {
if hasTags {
BookmarkTagsView(
tags: bookmark.tags,
maxVisible: style == .large ? 4 : 3
)
}
if hasTags && hasNote {
Text(verbatim: "·")
.font(.caption2)
.foregroundStyle(.secondary)
}
if hasNote {
Text(bookmark.note!)
.font(.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
}
}