mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Add tappable description links and timestamps in iOS
This commit is contained in:
parent
eeda7a5c6e
commit
97fc8fa4b7
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -499,6 +499,22 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
range: nil
|
||||
)
|
||||
|
||||
let linkRegex = #"(<a\s+(?:[^>]*?\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: "",
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -140,7 +140,6 @@ extension Defaults.Keys {
|
||||
static let trendingCountry = Key<Country>("trendingCountry", default: .us)
|
||||
|
||||
static let visibleSections = Key<Set<VisibleSection>>("visibleSections", default: [.favorites, .subscriptions, .trending, .playlists])
|
||||
static let videoDetailsPage = Key<VideoDetails.DetailsPage>("videoDetailsPage", default: .info)
|
||||
|
||||
#if os(iOS)
|
||||
static let honorSystemOrientationLock = Key<Bool>("honorSystemOrientationLock", default: true)
|
||||
|
@ -101,6 +101,8 @@ struct OpenURLHandler {
|
||||
Windows.main.open()
|
||||
#endif
|
||||
|
||||
player.videoBeingOpened = Video(videoID: id)
|
||||
|
||||
player.playerAPI.video(id)
|
||||
.load()
|
||||
.onSuccess { response in
|
||||
|
@ -102,8 +102,6 @@ struct PlayerControls: View {
|
||||
if model.presentingDetailsOverlay {
|
||||
VideoDetailsOverlay()
|
||||
.frame(maxWidth: detailsWidth, maxHeight: detailsHeight)
|
||||
.modifier(ControlBackgroundModifier())
|
||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||
.transition(.opacity)
|
||||
}
|
||||
|
||||
|
@ -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<Bool> {
|
||||
|
147
Shared/Player/VideoDescription.swift
Normal file
147
Shared/Player/VideoDescription.swift
Normal file
@ -0,0 +1,147 @@
|
||||
#if os(iOS)
|
||||
import ActiveLabel
|
||||
#endif
|
||||
import Defaults
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct VideoDescription: View {
|
||||
@EnvironmentObject<NavigationModel> private var navigation
|
||||
@EnvironmentObject<PlayerModel> private var player
|
||||
@EnvironmentObject<RecentsModel> private var recents
|
||||
@EnvironmentObject<SearchModel> 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<PlayerModel> 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()
|
||||
}
|
||||
}
|
@ -33,6 +33,7 @@ struct VideoDetails: View {
|
||||
@StateObject private var page: Page = .first()
|
||||
|
||||
@Environment(\.navigationStyle) private var navigationStyle
|
||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
@EnvironmentObject<CommentsModel> private var comments
|
||||
@ -41,8 +42,6 @@ struct VideoDetails: View {
|
||||
@EnvironmentObject<RecentsModel> private var recents
|
||||
@EnvironmentObject<SubscriptionsModel> 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 {
|
||||
|
@ -303,6 +303,11 @@ struct VideoPlayerView: View {
|
||||
playerSize: player.playerSize,
|
||||
fullScreen: fullScreenDetails
|
||||
))
|
||||
.onDisappear {
|
||||
if player.presentingPlayer {
|
||||
player.setNeedsDrawing(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -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 = "<group>"; };
|
||||
37CEE4BC2677B670005A1EFE /* SingleAssetStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleAssetStream.swift; sourceTree = "<group>"; };
|
||||
37CEE4C02677B697005A1EFE /* Stream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stream.swift; sourceTree = "<group>"; };
|
||||
37CFB48428AFE2510070024C /* VideoDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDescription.swift; sourceTree = "<group>"; };
|
||||
37D4B0C22671614700C925CA /* YatteeApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YatteeApp.swift; sourceTree = "<group>"; };
|
||||
37D4B0C32671614700C925CA /* AppTabNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTabNavigation.swift; sourceTree = "<group>"; };
|
||||
37D4B0C42671614800C925CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
@ -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" */;
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user