Let a row show the metadata it was handed

This commit is contained in:
Arkadiusz Fal
2026-09-07 07:30:00 +02:00
parent 69aed50012
commit dca8e2fbe4
12 changed files with 223 additions and 79 deletions

View File

@@ -85,6 +85,41 @@ struct Video: Identifiable, Codable, Sendable {
self.relatedVideos = relatedVideos
}
// MARK: - Standing in for metadata
/// A video that is nothing but its own ID, for a list that knows a video was
/// watched before it knows anything else about it.
///
/// An account's server-side history is a list of IDs; the rows have to be
/// on screen while their metadata is still being fetched, and one whose
/// fetch fails is still part of that history.
static func metadataPlaceholder(for videoID: String) -> Video {
Video(
id: .global(videoID),
title: videoID,
description: nil,
author: Author(id: "", name: ""),
duration: 0,
publishedAt: nil,
publishedText: nil,
viewCount: nil,
likeCount: nil,
thumbnails: [],
isLive: false,
isUpcoming: false,
scheduledStartTime: nil
)
}
/// Whether the title is only the video's own ID standing in for one.
///
/// Anything showing this video beside details it fetched itself should
/// prefer the fetched title: a bare ID is not a name anybody chose, and the
/// screen would otherwise keep it under a real description and thumbnail.
var hasPlaceholderTitle: Bool {
title == id.videoID
}
// MARK: - Computed Properties
var bestThumbnail: Thumbnail? {
@@ -429,17 +464,39 @@ extension Video {
// MARK: - Equatable & Hashable
extension Video: Equatable {
/// Custom Equatable implementation that only compares by ID for performance.
/// Two videos with the same ID are considered equal during SwiftUI diffing.
/// This avoids expensive comparisons of all properties (including nested Author and [Thumbnail]).
/// Compares what the video *is*, not only which video it is.
///
/// Being the same video is not the same as being the same *description* of
/// it. One ID can arrive first as a placeholder standing in for metadata
/// nobody has fetched yet and again as the real thing, and SwiftUI decides
/// whether a view needs redrawing by comparing the values it was given — so
/// equality by ID alone leaves a list showing the placeholder it was handed
/// first, however many times it is given the answer.
///
/// The ID is compared first, which is what nearly every comparison differs
/// in and settles it in one step.
static func == (lhs: Video, rhs: Video) -> Bool {
lhs.id == rhs.id
&& lhs.title == rhs.title
&& lhs.author == rhs.author
&& lhs.duration == rhs.duration
&& lhs.viewCount == rhs.viewCount
&& lhs.likeCount == rhs.likeCount
&& lhs.publishedAt == rhs.publishedAt
&& lhs.publishedText == rhs.publishedText
&& lhs.isLive == rhs.isLive
&& lhs.isUpcoming == rhs.isUpcoming
&& lhs.scheduledStartTime == rhs.scheduledStartTime
&& lhs.thumbnails == rhs.thumbnails
&& lhs.description == rhs.description
&& lhs.relatedVideos == rhs.relatedVideos
}
}
extension Video: Hashable {
/// Custom Hashable implementation consistent with Equatable.
/// Only hashes the ID since equality is determined by ID alone.
/// Hashed by ID alone, which stays coarser than equality on purpose: it is
/// what every lookup is by, and hashing equal for values that turn out to
/// differ is exactly what a hash is allowed to do.
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}