From 97fc8fa4b76444d21ebe1a0f3d5edb8246a2afa2 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 19 Aug 2022 23:55:02 +0200 Subject: [PATCH] Add tappable description links and timestamps in iOS --- Extensions/String+Format.swift | 25 +++ Fixtures/View+Fixtures.swift | 20 ++- Model/Applications/PipedAPI.swift | 16 ++ Model/NavigationModel.swift | 33 +++- Model/Video.swift | 12 +- Shared/Defaults.swift | 1 - Shared/OpenURLHandler.swift | 2 + Shared/Player/Controls/PlayerControls.swift | 2 - .../Player/Controls/VideoDetailsOverlay.swift | 2 + Shared/Player/VideoDescription.swift | 147 ++++++++++++++++++ Shared/Player/VideoDetails.swift | 79 ++++------ Shared/Player/VideoPlayerView.swift | 5 + Yattee.xcodeproj/project.pbxproj | 25 +++ .../xcshareddata/swiftpm/Package.resolved | 9 ++ 14 files changed, 308 insertions(+), 70 deletions(-) create mode 100644 Shared/Player/VideoDescription.swift diff --git a/Extensions/String+Format.swift b/Extensions/String+Format.swift index c35f327a..1ba2142e 100644 --- a/Extensions/String+Format.swift +++ b/Extensions/String+Format.swift @@ -7,4 +7,29 @@ extension String { } return replacingCharacters(in: range, with: replacement) } + + func replacingMatches(regex: String, replacementStringClosure: (String) -> String?) -> String { + guard let regex = try? NSRegularExpression(pattern: regex) else { + return self + } + + let results = regex.matches(in: self, range: NSRange(startIndex..., in: self)) + + var outputText = self + + results.reversed().forEach { match in + (1 ..< match.numberOfRanges).reversed().forEach { rangeIndex in + let matchingGroup: String = (self as NSString).substring(with: match.range(at: rangeIndex)) + let rangeBounds = match.range(at: rangeIndex) + + guard let range = Range(rangeBounds, in: self) else { + return + } + let replacement = replacementStringClosure(matchingGroup) ?? matchingGroup + + outputText = outputText.replacingOccurrences(of: matchingGroup, with: replacement, range: range) + } + } + return outputText + } } diff --git a/Fixtures/View+Fixtures.swift b/Fixtures/View+Fixtures.swift index da7eb176..8f717ef0 100644 --- a/Fixtures/View+Fixtures.swift +++ b/Fixtures/View+Fixtures.swift @@ -43,9 +43,25 @@ struct FixtureEnvironmentObjectsModifier: ViewModifier { private var player: PlayerModel { let player = PlayerModel() - player.currentItem = PlayerQueueItem(Video(videoID: "", title: "", author: "", length: 0, published: "2 days ago", views: 43434, channel: .init(id: "", name: ""), likes: 2332, dislikes: 30)) + player.currentItem = PlayerQueueItem( + Video( + videoID: "", + title: "", + author: "", + length: 0, + published: "2 days ago", + views: 43434, + description: "The 14\" and 16\" MacBook Pros are incredible. I can finally retire the travel iMac.\nThat shirt! http://shop.MKBHD.com\nMacBook Pro skins: https://dbrand.com/macbooks\n\n0:00 Intro\n1:38 Top Notch Design\n2:27 Let's Talk Ports\n7:11 RIP Touchbar\n8:20 The new displays\n10:12 Living with the notch\n12:37 Performance\n19:39 Battery\n20:30 So should you get it?\n\nThe Verge Review: https://youtu.be/ftU1HzBKd5Y\nTyler Stalman Review: https://youtu.be/I10WMJV96ns\nDeveloper's tweet: https://twitter.com/softwarejameson/status/1455971162060697613?s=09&t=WbOkVKgDdcegIdyOdurSNQ&utm_source=pocket_mylist\n\nTech I'm using right now: https://www.amazon.com/shop/MKBHD\n\nIntro Track: http://youtube.com/20syl\nPlaylist of MKBHD Intro music: https://goo.gl/B3AWV5\n\nLaptop provided by Apple for review.\n\n~\nhttp://twitter.com/MKBHD\nhttp://instagram.com/MKBHD\nhttp://facebook.com/MKBHD", + channel: .init(id: "", name: "Channel Name"), + likes: 2332, + dislikes: 30, + keywords: ["Video", "Computer", "Long Long Keyword"] + ) + ) + #if os(iOS) + player.playerSize = .init(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) + #endif player.queue = Video.allFixtures.map { PlayerQueueItem($0) } - player.videoBeingOpened = Video.fixture return player } diff --git a/Model/Applications/PipedAPI.swift b/Model/Applications/PipedAPI.swift index 27d421d3..e47e5271 100644 --- a/Model/Applications/PipedAPI.swift +++ b/Model/Applications/PipedAPI.swift @@ -499,6 +499,22 @@ final class PipedAPI: Service, ObservableObject, VideosAPI { range: nil ) + let linkRegex = #"(]*?\s+)?href=\"[^"]*\">[^<]*<\/a>)"# + let hrefRegex = #"href=\"([^"]*)\">"# + guard let hrefRegex = try? NSRegularExpression(pattern: hrefRegex) else { return description } + + description = description.replacingMatches(regex: linkRegex) { matchingGroup in + let results = hrefRegex.matches(in: matchingGroup, range: NSRange(matchingGroup.startIndex..., in: matchingGroup)) + + if let result = results.first { + if let swiftRange = Range(result.range(at: 1), in: matchingGroup) { + return String(matchingGroup[swiftRange]) + } + } + + return matchingGroup + } + description = description.replacingOccurrences( of: "<[^>]+>", with: "", diff --git a/Model/NavigationModel.swift b/Model/NavigationModel.swift index b2d885bd..d591e576 100644 --- a/Model/NavigationModel.swift +++ b/Model/NavigationModel.swift @@ -96,6 +96,7 @@ final class NavigationModel: ObservableObject { } navigation.hideKeyboard() + let presentingPlayer = player.presentingPlayer player.hide() navigation.presentingChannel = false @@ -110,8 +111,14 @@ final class NavigationModel: ObservableObject { navigation.sidebarSectionChanged.toggle() navigation.tabSelection = .recentlyOpened(recent.tag) } else { - withAnimation(.linear(duration: 0.3)) { - navigation.presentingChannel = true + var delay = 0.0 + #if os(iOS) + if presentingPlayer { delay = 1.0 } + #endif + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + withAnimation(.linear(duration: 0.3)) { + navigation.presentingChannel = true + } } } } @@ -134,6 +141,8 @@ final class NavigationModel: ObservableObject { #endif navigation.hideKeyboard() + let presentingPlayer = player.presentingPlayer + player.hide() DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { recents.add(recent) @@ -142,8 +151,14 @@ final class NavigationModel: ObservableObject { navigation.sidebarSectionChanged.toggle() navigation.tabSelection = .recentlyOpened(recent.tag) } else { - withAnimation(.linear(duration: 0.3)) { - navigation.presentingPlaylist = true + var delay = 0.0 + #if os(iOS) + if presentingPlayer { delay = 1.0 } + #endif + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + withAnimation(.linear(duration: 0.3)) { + navigation.presentingPlaylist = true + } } } } @@ -156,18 +171,24 @@ final class NavigationModel: ObservableObject { navigation: NavigationModel, search: SearchModel ) { - player.hide() navigation.presentingChannel = false navigation.presentingPlaylist = false navigation.tabSelection = .search navigation.hideKeyboard() + let presentingPlayer = player.presentingPlayer + player.hide() + if let searchQuery = searchQuery { let recent = RecentItem(from: searchQuery) recents.add(recent) - DispatchQueue.main.async { + var delay = 0.0 + #if os(iOS) + if presentingPlayer { delay = 1.0 } + #endif + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { search.queryText = searchQuery search.changeQuery { query in query.query = searchQuery } } diff --git a/Model/Video.swift b/Model/Video.swift index 497ce4ae..a73568dd 100644 --- a/Model/Video.swift +++ b/Model/Video.swift @@ -39,14 +39,14 @@ struct Video: Identifiable, Equatable, Hashable { init( id: String? = nil, videoID: String, - title: String, - author: String, - length: TimeInterval, - published: String, - views: Int, + title: String = "", + author: String = "", + length: TimeInterval = .zero, + published: String = "", + views: Int = 0, description: String? = nil, genre: String? = nil, - channel: Channel, + channel: Channel = .init(id: "", name: ""), thumbnails: [Thumbnail] = [], indexID: String? = nil, live: Bool = false, diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index 07f2719a..7583aaea 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -140,7 +140,6 @@ extension Defaults.Keys { static let trendingCountry = Key("trendingCountry", default: .us) static let visibleSections = Key>("visibleSections", default: [.favorites, .subscriptions, .trending, .playlists]) - static let videoDetailsPage = Key("videoDetailsPage", default: .info) #if os(iOS) static let honorSystemOrientationLock = Key("honorSystemOrientationLock", default: true) diff --git a/Shared/OpenURLHandler.swift b/Shared/OpenURLHandler.swift index 29a94c64..09fbc652 100644 --- a/Shared/OpenURLHandler.swift +++ b/Shared/OpenURLHandler.swift @@ -101,6 +101,8 @@ struct OpenURLHandler { Windows.main.open() #endif + player.videoBeingOpened = Video(videoID: id) + player.playerAPI.video(id) .load() .onSuccess { response in diff --git a/Shared/Player/Controls/PlayerControls.swift b/Shared/Player/Controls/PlayerControls.swift index e1c4ff46..ae46490b 100644 --- a/Shared/Player/Controls/PlayerControls.swift +++ b/Shared/Player/Controls/PlayerControls.swift @@ -102,8 +102,6 @@ struct PlayerControls: View { if model.presentingDetailsOverlay { VideoDetailsOverlay() .frame(maxWidth: detailsWidth, maxHeight: detailsHeight) - .modifier(ControlBackgroundModifier()) - .clipShape(RoundedRectangle(cornerRadius: 4)) .transition(.opacity) } diff --git a/Shared/Player/Controls/VideoDetailsOverlay.swift b/Shared/Player/Controls/VideoDetailsOverlay.swift index 8644fc1e..a1088a10 100644 --- a/Shared/Player/Controls/VideoDetailsOverlay.swift +++ b/Shared/Player/Controls/VideoDetailsOverlay.swift @@ -6,6 +6,8 @@ struct VideoDetailsOverlay: View { var body: some View { VideoDetails(sidebarQueue: false, fullScreen: fullScreenBinding) + .modifier(ControlBackgroundModifier()) + .clipShape(RoundedRectangle(cornerRadius: 4)) } var fullScreenBinding: Binding { diff --git a/Shared/Player/VideoDescription.swift b/Shared/Player/VideoDescription.swift new file mode 100644 index 00000000..f48355d7 --- /dev/null +++ b/Shared/Player/VideoDescription.swift @@ -0,0 +1,147 @@ +#if os(iOS) + import ActiveLabel +#endif +import Defaults +import Foundation +import SwiftUI + +struct VideoDescription: View { + @EnvironmentObject private var navigation + @EnvironmentObject private var player + @EnvironmentObject private var recents + @EnvironmentObject private var search + @Default(.showKeywords) private var showKeywords + + var video: Video + var detailsSize: CGSize? + + var description: String { + video.description ?? "" + } + + var body: some View { + VStack { + #if os(iOS) + ActiveLabelDescriptionRepresentable(description: description, detailsSize: detailsSize) + #else + textDescription + #endif + + keywords + } + } + + @ViewBuilder var textDescription: some View { + #if !os(iOS) + Group { + if #available(macOS 12, *) { + Text(description) + #if !os(tvOS) + .textSelection(.enabled) + #endif + } else { + Text(description) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .font(.system(size: 14)) + .lineSpacing(3) + #endif + } + + @ViewBuilder var keywords: some View { + if showKeywords { + ScrollView(.horizontal, showsIndicators: showScrollIndicators) { + HStack { + ForEach(video.keywords, id: \.self) { keyword in + Button { + NavigationModel.openSearchQuery(keyword, player: player, recents: recents, navigation: navigation, search: search) + } label: { + HStack(alignment: .center, spacing: 0) { + Text("#") + .font(.system(size: 14).bold()) + + Text(keyword) + .frame(maxWidth: 500) + } + .font(.caption) + .foregroundColor(.white) + .padding(.vertical, 4) + .padding(.horizontal, 8) + .background(Color("KeywordBackgroundColor")) + .mask(RoundedRectangle(cornerRadius: 3)) + } + .buttonStyle(.plain) + } + } + } + } + } + + var showScrollIndicators: Bool { + #if os(macOS) + false + #else + true + #endif + } +} + +#if os(iOS) + struct ActiveLabelDescriptionRepresentable: UIViewRepresentable { + var description: String + var detailsSize: CGSize? + + @State private var label = ActiveLabel() + + @Environment(\.openURL) private var openURL + @EnvironmentObject private var player + + func makeUIView(context _: Context) -> some UIView { + customizeLabel() + return label + } + + func updateUIView(_: UIViewType, context _: Context) { + customizeLabel() + } + + func customizeLabel() { + label.customize { label in + label.enabledTypes = [.url, .timestamp] + label.numberOfLines = 0 + label.text = description + label.contentMode = .scaleAspectFill + label.font = .systemFont(ofSize: 14) + label.lineSpacing = 3 + label.preferredMaxLayoutWidth = (detailsSize?.width ?? 330) - 30 + label.URLColor = UIColor(Color.accentColor) + label.timestampColor = UIColor(Color.accentColor) + label.handleURLTap { url in + var urlToOpen = url + + if var components = URLComponents(url: url, resolvingAgainstBaseURL: false) { + components.scheme = "yattee" + if let yatteeURL = components.url, + URLParser(url: urlToOpen).destination != nil + { + urlToOpen = yatteeURL + } + } + + openURL(urlToOpen) + } + label.handleTimestampTap { timestamp in + player.backend.seek(to: timestamp.timeInterval) + } + } + } + } +#endif + +struct VideoDescription_Previews: PreviewProvider { + static var previews: some View { + VideoDescription(video: .fixture) + .injectFixtureEnvironmentObjects() + } +} diff --git a/Shared/Player/VideoDetails.swift b/Shared/Player/VideoDetails.swift index 37f29dea..b269fccb 100644 --- a/Shared/Player/VideoDetails.swift +++ b/Shared/Player/VideoDetails.swift @@ -33,6 +33,7 @@ struct VideoDetails: View { @StateObject private var page: Page = .first() @Environment(\.navigationStyle) private var navigationStyle + @Environment(\.verticalSizeClass) private var verticalSizeClass @EnvironmentObject private var accounts @EnvironmentObject private var comments @@ -41,8 +42,6 @@ struct VideoDetails: View { @EnvironmentObject private var recents @EnvironmentObject private var subscriptions - @Default(.videoDetailsPage) private var videoDetailsPage - @Default(.showKeywords) private var showKeywords @Default(.playerDetailsPageButtonLabelStyle) private var playerDetailsPageButtonLabelStyle var currentPage: DetailsPage { @@ -92,12 +91,10 @@ struct VideoDetails: View { if pageIndex == DetailsPage.comments.index { comments.load() } - - videoDetailsPage = DetailsPage.allCases.first { $0.index == pageIndex } ?? .info } } .onAppear { - page.update(.new(index: videoDetailsPage.index)) + page.update(.moveToFirst) guard video != nil, accounts.app.supportsSubscriptions else { subscribed = false @@ -114,11 +111,20 @@ struct VideoDetails: View { } } .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) + .overlay(GeometryReader { proxy in + Color.clear + .onAppear { + detailsSize = proxy.size + } + .onChange(of: proxy.size) { newSize in + detailsSize = newSize + } + }) } var publishedDateSection: some View { Group { - if let video = player.currentVideo { + if let video = video { HStack(spacing: 4) { if let published = video.publishedDate { Text(published) @@ -144,7 +150,6 @@ struct VideoDetails: View { Button(action: { page.update(.new(index: destination.index)) pageChangeAction?() - videoDetailsPage = destination }) { HStack { Spacer() @@ -199,10 +204,12 @@ struct VideoDetails: View { .contentShape(Rectangle()) } + @State private var detailsSize = CGSize.zero + var detailsPage: some View { Group { VStack(alignment: .leading, spacing: 0) { - if let video = player.currentVideo { + if let video = video { VStack(spacing: 6) { videoProperties @@ -218,47 +225,13 @@ struct VideoDetails: View { } } .redacted(reason: .placeholder) - } else if let description = video.description { - Group { - if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) { - Text(description) - #if !os(tvOS) - .textSelection(.enabled) - #endif - } else { - Text(description) - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .font(.system(size: 14)) - .lineSpacing(3) + } else if video.description != nil, !video.description!.isEmpty { + VideoDescription(video: video, detailsSize: detailsSize) + .padding(.bottom, fullScreenLayout ? 10 : SafeArea.insets.bottom) } else { Text("No description") .foregroundColor(.secondary) } - - if showKeywords { - ScrollView(.horizontal, showsIndicators: showScrollIndicators) { - HStack { - ForEach(video.keywords, id: \.self) { keyword in - HStack(alignment: .center, spacing: 0) { - Text("#") - .font(.system(size: 11).bold()) - - Text(keyword) - .frame(maxWidth: 500) - } - .font(.caption) - .foregroundColor(.white) - .padding(.vertical, 4) - .padding(.horizontal, 8) - .background(Color("KeywordBackgroundColor")) - .mask(RoundedRectangle(cornerRadius: 3)) - } - } - .padding(.bottom, 10) - } - } } } } @@ -266,6 +239,14 @@ struct VideoDetails: View { } } + var fullScreenLayout: Bool { + #if os(iOS) + return player.playingFullScreen || verticalSizeClass == .compact + #else + return player.playingFullScreen + #endif + } + @ViewBuilder var videoProperties: some View { HStack(spacing: 2) { publishedDateSection @@ -319,14 +300,6 @@ struct VideoDetails: View { .frame(maxWidth: 100) } - - var showScrollIndicators: Bool { - #if os(macOS) - false - #else - true - #endif - } } struct VideoDetails_Previews: PreviewProvider { diff --git a/Shared/Player/VideoPlayerView.swift b/Shared/Player/VideoPlayerView.swift index 895ed79e..a0c1d84a 100644 --- a/Shared/Player/VideoPlayerView.swift +++ b/Shared/Player/VideoPlayerView.swift @@ -303,6 +303,11 @@ struct VideoPlayerView: View { playerSize: player.playerSize, fullScreen: fullScreenDetails )) + .onDisappear { + if player.presentingPlayer { + player.setNeedsDrawing(true) + } + } } #endif } diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index 0d8bced0..b9a1a2f4 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -528,6 +528,7 @@ 379775932689365600DD52A8 /* Array+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379775922689365600DD52A8 /* Array+Next.swift */; }; 379775942689365600DD52A8 /* Array+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379775922689365600DD52A8 /* Array+Next.swift */; }; 379775952689365600DD52A8 /* Array+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379775922689365600DD52A8 /* Array+Next.swift */; }; + 3799AC0928B03CED001376F9 /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 3799AC0828B03CED001376F9 /* ActiveLabel */; }; 379B0253287A1CDF001015B5 /* OrientationTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379B0252287A1CDF001015B5 /* OrientationTracker.swift */; }; 379F141F289ECE7F00DE48B5 /* QualitySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379F141E289ECE7F00DE48B5 /* QualitySettings.swift */; }; 379F1420289ECE7F00DE48B5 /* QualitySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379F141E289ECE7F00DE48B5 /* QualitySettings.swift */; }; @@ -685,6 +686,9 @@ 37CF8B8428535E4F00B71E37 /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = 37CF8B8328535E4F00B71E37 /* SDWebImage */; }; 37CF8B8628535E5A00B71E37 /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = 37CF8B8528535E5A00B71E37 /* SDWebImage */; }; 37CF8B8828535E6300B71E37 /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = 37CF8B8728535E6300B71E37 /* SDWebImage */; }; + 37CFB48528AFE2510070024C /* VideoDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CFB48428AFE2510070024C /* VideoDescription.swift */; }; + 37CFB48628AFE2510070024C /* VideoDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CFB48428AFE2510070024C /* VideoDescription.swift */; }; + 37CFB48728AFE2510070024C /* VideoDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CFB48428AFE2510070024C /* VideoDescription.swift */; }; 37D4B0D92671614900C925CA /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0D82671614900C925CA /* Tests_iOS.swift */; }; 37D4B0E32671614900C925CA /* Tests_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0E22671614900C925CA /* Tests_macOS.swift */; }; 37D4B0E42671614900C925CA /* YatteeApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C22671614700C925CA /* YatteeApp.swift */; }; @@ -1187,6 +1191,7 @@ 37CC3F4F270D010D00608308 /* VideoBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoBanner.swift; sourceTree = ""; }; 37CEE4BC2677B670005A1EFE /* SingleAssetStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleAssetStream.swift; sourceTree = ""; }; 37CEE4C02677B697005A1EFE /* Stream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stream.swift; sourceTree = ""; }; + 37CFB48428AFE2510070024C /* VideoDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDescription.swift; sourceTree = ""; }; 37D4B0C22671614700C925CA /* YatteeApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YatteeApp.swift; sourceTree = ""; }; 37D4B0C32671614700C925CA /* AppTabNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTabNavigation.swift; sourceTree = ""; }; 37D4B0C42671614800C925CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -1287,6 +1292,7 @@ 37C2212B27ADA43700305B41 /* VideoToolbox.framework in Frameworks */, 3736A214286BB72300C9E5EE /* libswscale.xcframework in Frameworks */, 3736A216286BB72300C9E5EE /* libavfilter.xcframework in Frameworks */, + 3799AC0928B03CED001376F9 /* ActiveLabel in Frameworks */, 3736A218286BB72300C9E5EE /* libharfbuzz.xcframework in Frameworks */, 3736A206286BB72300C9E5EE /* libfreetype.xcframework in Frameworks */, 37C2212927ADA41400305B41 /* CoreMedia.framework in Frameworks */, @@ -1539,6 +1545,7 @@ 3795593527B08538007FF8F4 /* StreamControl.swift */, 37B81AFE26D2CA3700675966 /* VideoDetails.swift */, 37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */, + 37CFB48428AFE2510070024C /* VideoDescription.swift */, 37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */, 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */, 37F9619A27BD89E000058149 /* TapRecognizerViewModifier.swift */, @@ -2270,6 +2277,7 @@ 37A5DBC3285DFF5400CA4DD1 /* SwiftUIPager */, 372AA40F286D067B0000B1DC /* Repeat */, 37EE6DC428A305AD00BFD632 /* Reachability */, + 3799AC0828B03CED001376F9 /* ActiveLabel */, ); productName = "Yattee (iOS)"; productReference = 37D4B0C92671614900C925CA /* Yattee.app */; @@ -2480,6 +2488,7 @@ 37A5DBC2285DFF5400CA4DD1 /* XCRemoteSwiftPackageReference "SwiftUIPager" */, 372AA40E286D067B0000B1DC /* XCRemoteSwiftPackageReference "Repeat" */, 37EE6DC328A305AD00BFD632 /* XCRemoteSwiftPackageReference "Reachability" */, + 3799AC0728B03CEC001376F9 /* XCRemoteSwiftPackageReference "ActiveLabel.swift" */, ); productRefGroup = 37D4B0CA2671614900C925CA /* Products */; projectDirPath = ""; @@ -2860,6 +2869,7 @@ 37B17DA2268A1F8A006AEE9B /* VideoContextMenuView.swift in Sources */, 3784B23B272894DA00B09468 /* ShareSheet.swift in Sources */, 379775932689365600DD52A8 /* Array+Next.swift in Sources */, + 37CFB48528AFE2510070024C /* VideoDescription.swift in Sources */, 37B81AFC26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */, 37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */, 37BA794F26DC3E0E002A0235 /* Int+Format.swift in Sources */, @@ -3124,6 +3134,7 @@ 37C3A252272366440087A57A /* ChannelPlaylistView.swift in Sources */, 373CFADC269663F1003CB2C6 /* Thumbnail.swift in Sources */, 37C0697B2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */, + 37CFB48628AFE2510070024C /* VideoDescription.swift in Sources */, 37C3A24A27235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */, 377ABC41286E4AD5009C986F /* InstancesManifest.swift in Sources */, 373CFAF02697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */, @@ -3236,6 +3247,7 @@ 37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */, 37E70925271CD43000D34DDE /* WelcomeScreen.swift in Sources */, 376BE50D27349108009AD608 /* BrowsingSettings.swift in Sources */, + 37CFB48728AFE2510070024C /* VideoDescription.swift in Sources */, 37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */, 378AE93E274EDFB4006A4EE1 /* Tint+Backport.swift in Sources */, 37FFC442272734C3009FFD26 /* Throttle.swift in Sources */, @@ -4304,6 +4316,14 @@ minimumVersion = 1.5.0; }; }; + 3799AC0728B03CEC001376F9 /* XCRemoteSwiftPackageReference "ActiveLabel.swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/yattee/ActiveLabel.swift.git"; + requirement = { + branch = feature/timestamp; + kind = branch; + }; + }; 37A5DBC2285DFF5400CA4DD1 /* XCRemoteSwiftPackageReference "SwiftUIPager" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/fermoya/SwiftUIPager.git"; @@ -4502,6 +4522,11 @@ package = 3797757B268922D100DD52A8 /* XCRemoteSwiftPackageReference "siesta" */; productName = Siesta; }; + 3799AC0828B03CED001376F9 /* ActiveLabel */ = { + isa = XCSwiftPackageProductDependency; + package = 3799AC0728B03CEC001376F9 /* XCRemoteSwiftPackageReference "ActiveLabel.swift" */; + productName = ActiveLabel; + }; 37A5DBC3285DFF5400CA4DD1 /* SwiftUIPager */ = { isa = XCSwiftPackageProductDependency; package = 37A5DBC2285DFF5400CA4DD1 /* XCRemoteSwiftPackageReference "SwiftUIPager" */; diff --git a/Yattee.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Yattee.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a80758f7..e987479b 100644 --- a/Yattee.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Yattee.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "activelabel.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/yattee/ActiveLabel.swift.git", + "state" : { + "branch" : "feature/timestamp", + "revision" : "2a88b80e44f84aa614032466039307c65815b2f3" + } + }, { "identity" : "alamofire", "kind" : "remoteSourceControl",