diff --git a/Apple TV/VideoCellView.swift b/Apple TV/VideoCellView.swift index fdbcf0e0..0baee458 100644 --- a/Apple TV/VideoCellView.swift +++ b/Apple TV/VideoCellView.swift @@ -10,7 +10,7 @@ struct VideoCellView: View { NavigationLink(destination: PlayerView(id: video.id)) { VStack(alignment: .leading) { ZStack(alignment: .trailing) { - if let thumbnail = video.thumbnailURL(quality: "high") { + if let thumbnail = video.thumbnailURL(quality: .high) { // to replace with AsyncImage when it is fixed with lazy views URLImage(thumbnail) { image in image diff --git a/Apple TV/VideoDetailsView.swift b/Apple TV/VideoDetailsView.swift index c2197c08..1522b858 100644 --- a/Apple TV/VideoDetailsView.swift +++ b/Apple TV/VideoDetailsView.swift @@ -31,7 +31,7 @@ struct VideoDetailsView: View { VStack(alignment: .center) { ZStack(alignment: .bottom) { Group { - if let thumbnail = video.thumbnailURL(quality: "maxres") { + if let thumbnail = video.thumbnailURL(quality: .maxres) { // to replace with AsyncImage when it is fixed with lazy views URLImage(thumbnail) { image in image diff --git a/Apple TV/VideoListRowView.swift b/Apple TV/VideoListRowView.swift index 2d370a95..46185427 100644 --- a/Apple TV/VideoListRowView.swift +++ b/Apple TV/VideoListRowView.swift @@ -16,7 +16,7 @@ struct VideoListRowView: View { NavigationLink(destination: PlayerView(id: video.id)) { #if os(tvOS) horizontalRow(detailsOnThumbnail: false) - #else + #elseif os(macOS) verticalRow #endif } @@ -41,11 +41,10 @@ struct VideoListRowView: View { func horizontalRow(detailsOnThumbnail: Bool = true, padding: Double = 0) -> some View { HStack(alignment: .top, spacing: 2) { if detailsOnThumbnail { - thumbnailWithDetails + thumbnailWithDetails() .padding(padding) } else { - thumbnail - .frame(width: 320, height: 180) + thumbnail(.medium, maxWidth: 320, maxHeight: 180) } VStack(alignment: .leading, spacing: 0) { @@ -82,8 +81,8 @@ struct VideoListRowView: View { var verticalRow: some View { VStack(alignment: .leading) { - thumbnailWithDetails - .frame(minWidth: 0, maxWidth: 600) + thumbnailWithDetails(minWidth: 250, maxWidth: 600, minHeight: 180) + .frame(idealWidth: 320) .padding([.leading, .top, .trailing], 4) VStack(alignment: .leading) { @@ -121,46 +120,67 @@ struct VideoListRowView: View { } } - var thumbnailWithDetails: some View { + func thumbnailWithDetails( + minWidth: Double = 250, + maxWidth: Double = .infinity, + minHeight: Double = 140, + maxHeight: Double = .infinity + ) -> some View { ZStack(alignment: .trailing) { - thumbnail + thumbnail(.maxres, minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight) VStack(alignment: .trailing) { - Text(video.author) - .padding(8) - .background(.thinMaterial) - .mask(RoundedRectangle(cornerRadius: 12)) + detailOnThinMaterial(video.author) .offset(x: -5, y: 5) Spacer() if let time = video.playTime { - Text(time) - .fontWeight(.bold) - .padding(8) - .background(.thinMaterial) - .mask(RoundedRectangle(cornerRadius: 12)) + detailOnThinMaterial(time, bold: true) .offset(x: -5, y: -5) } } } } - var thumbnail: some View { + func detailOnThinMaterial(_ text: String, bold: Bool = false) -> some View { + Text(text) + .fontWeight(bold ? .semibold : .regular) + .padding(8) + .background(.thinMaterial) + .mask(RoundedRectangle(cornerRadius: 12)) + } + + func thumbnail( + _ quality: ThumbnailQuality, + minWidth: Double = 320, + maxWidth: Double = .infinity, + minHeight: Double = 180, + maxHeight: Double = .infinity + ) -> some View { Group { - if let thumbnail = video.thumbnailURL(quality: "maxres") { - // to replace with AsyncImage when it is fixed with lazy views - URLImage(thumbnail) { image in + if let url = video.thumbnailURL(quality: quality) { + URLImage(url) { + EmptyView() + } inProgress: { _ in + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + } failure: { _, retry in + VStack { + Button("Retry", action: retry) + } + } content: { image in image .resizable() .aspectRatio(contentMode: .fill) + .frame(minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight) } .mask(RoundedRectangle(cornerRadius: 12)) } else { Image(systemName: "exclamationmark.square") } } - .frame(minWidth: 320, maxWidth: .infinity, minHeight: 180, maxHeight: .infinity) + .frame(minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight) } func videoDetail(_ text: String, color: Color? = .primary, bold: Bool = false) -> some View { diff --git a/Model/PlayerState.swift b/Model/PlayerState.swift index 671df0ff..06d71ff7 100644 --- a/Model/PlayerState.swift +++ b/Model/PlayerState.swift @@ -40,7 +40,7 @@ final class PlayerState: ObservableObject { #if !os(macOS) - if let thumbnailData = try? Data(contentsOf: video.thumbnailURL(quality: "high")!), + if let thumbnailData = try? Data(contentsOf: video.thumbnailURL(quality: .high)!), let image = UIImage(data: thumbnailData), let pngData = image.pngData() { diff --git a/Model/Thumbnail.swift b/Model/Thumbnail.swift index 50c6a5e2..7340ad2b 100644 --- a/Model/Thumbnail.swift +++ b/Model/Thumbnail.swift @@ -3,10 +3,10 @@ import SwiftyJSON struct Thumbnail { var url: URL - var quality: String + var quality: ThumbnailQuality init(_ json: JSON) { url = json["url"].url! - quality = json["quality"].string! + quality = ThumbnailQuality(rawValue: json["quality"].string!)! } } diff --git a/Model/ThumbnailQuality.swift b/Model/ThumbnailQuality.swift new file mode 100644 index 00000000..5f63cfd9 --- /dev/null +++ b/Model/ThumbnailQuality.swift @@ -0,0 +1,5 @@ +import Foundation + +enum ThumbnailQuality: String { + case maxres, maxresdefault, sddefault, high, medium, `default`, start, middle, end +} diff --git a/Model/Video.swift b/Model/Video.swift index 1605a7e6..fd108022 100644 --- a/Model/Video.swift +++ b/Model/Video.swift @@ -107,7 +107,7 @@ struct Video: Identifiable { streamWithResolution(profile.defaultStreamResolution.value) ?? streams.first } - func thumbnailURL(quality: String) -> URL? { + func thumbnailURL(quality: ThumbnailQuality) -> URL? { thumbnails.first { $0.quality == quality }?.url } diff --git a/Pearvidious.xcodeproj/project.pbxproj b/Pearvidious.xcodeproj/project.pbxproj index ea9022d8..d01c0aa6 100644 --- a/Pearvidious.xcodeproj/project.pbxproj +++ b/Pearvidious.xcodeproj/project.pbxproj @@ -187,6 +187,9 @@ 37D4B19D2671817900C925CA /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 37D4B19C2671817900C925CA /* SwiftyJSON */; }; 37D4B1AB2672580400C925CA /* URLImage in Frameworks */ = {isa = PBXBuildFile; productRef = 37D4B1AA2672580400C925CA /* URLImage */; }; 37D4B1AD2672580400C925CA /* URLImageStore in Frameworks */ = {isa = PBXBuildFile; productRef = 37D4B1AC2672580400C925CA /* URLImageStore */; }; + 37D80701269C74F8002ECBBA /* ThumbnailQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D80700269C74F8002ECBBA /* ThumbnailQuality.swift */; }; + 37D80702269C74F8002ECBBA /* ThumbnailQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D80700269C74F8002ECBBA /* ThumbnailQuality.swift */; }; + 37D80703269C74F8002ECBBA /* ThumbnailQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D80700269C74F8002ECBBA /* ThumbnailQuality.swift */; }; 37EAD86B267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAD86A267B9C5600D9E01B /* SponsorBlockAPI.swift */; }; 37EAD86C267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAD86A267B9C5600D9E01B /* SponsorBlockAPI.swift */; }; 37EAD86D267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAD86A267B9C5600D9E01B /* SponsorBlockAPI.swift */; }; @@ -292,6 +295,7 @@ 37D4B18B26717B3800C925CA /* VideoListRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoListRowView.swift; sourceTree = ""; }; 37D4B19626717E1500C925CA /* Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Video.swift; sourceTree = ""; }; 37D4B1AE26729DEB00C925CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 37D80700269C74F8002ECBBA /* ThumbnailQuality.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailQuality.swift; sourceTree = ""; }; 37EAD86A267B9C5600D9E01B /* SponsorBlockAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorBlockAPI.swift; sourceTree = ""; }; 37EAD86E267B9ED100D9E01B /* Segment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Segment.swift; sourceTree = ""; }; 37F4AE7126828F0900BD60EA /* VideosCellsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosCellsView.swift; sourceTree = ""; }; @@ -508,6 +512,7 @@ 37CEE4B82677B63F005A1EFE /* StreamResolution.swift */, 37CEE4B42677B628005A1EFE /* StreamType.swift */, 373CFADA269663F1003CB2C6 /* Thumbnail.swift */, + 37D80700269C74F8002ECBBA /* ThumbnailQuality.swift */, 3705B181267B4E4900704544 /* TrendingCategory.swift */, 37D4B19626717E1500C925CA /* Video.swift */, ); @@ -833,6 +838,7 @@ 373CFAC226966159003CB2C6 /* CoverSectionRowView.swift in Sources */, 37141673267A8E10006CA35D /* Country.swift in Sources */, 37AAF2A026741C97007FC770 /* SubscriptionsView.swift in Sources */, + 37D80701269C74F8002ECBBA /* ThumbnailQuality.swift in Sources */, 373CFAD7269662CD003CB2C6 /* SearchDuration.swift in Sources */, 373CFACF26966290003CB2C6 /* SearchSortOrder.swift in Sources */, 373CFAEB26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */, @@ -853,6 +859,7 @@ buildActionMask = 2147483647; files = ( 37CEE4BE2677B670005A1EFE /* AudioVideoStream.swift in Sources */, + 37D80702269C74F8002ECBBA /* ThumbnailQuality.swift in Sources */, 37F4AE772682908700BD60EA /* VideoCellView.swift in Sources */, 373CFABF26966149003CB2C6 /* CoverSectionView.swift in Sources */, 37EAD86C267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */, @@ -957,6 +964,7 @@ 37D4B18E26717B3800C925CA /* VideoListRowView.swift in Sources */, 37AAF27E26737323007FC770 /* PopularVideosView.swift in Sources */, 37AAF29A26740A01007FC770 /* VideosListView.swift in Sources */, + 37D80703269C74F8002ECBBA /* ThumbnailQuality.swift in Sources */, 37AAF2962674086B007FC770 /* TabSelection.swift in Sources */, 37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */, 376578932685490700D4EA09 /* PlaylistsView.swift in Sources */, diff --git a/Pearvidious.xcodeproj/xcuserdata/arek.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Pearvidious.xcodeproj/xcuserdata/arek.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index e956de23..de72582d 100644 --- a/Pearvidious.xcodeproj/xcuserdata/arek.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Pearvidious.xcodeproj/xcuserdata/arek.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -84,5 +84,21 @@ landmarkType = "7"> + + + +