mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 21:43:41 +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)
|
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 {
|
private var player: PlayerModel {
|
||||||
let 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.queue = Video.allFixtures.map { PlayerQueueItem($0) }
|
||||||
player.videoBeingOpened = Video.fixture
|
|
||||||
|
|
||||||
return player
|
return player
|
||||||
}
|
}
|
||||||
|
@ -499,6 +499,22 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
|||||||
range: nil
|
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(
|
description = description.replacingOccurrences(
|
||||||
of: "<[^>]+>",
|
of: "<[^>]+>",
|
||||||
with: "",
|
with: "",
|
||||||
|
@ -96,6 +96,7 @@ final class NavigationModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
navigation.hideKeyboard()
|
navigation.hideKeyboard()
|
||||||
|
let presentingPlayer = player.presentingPlayer
|
||||||
player.hide()
|
player.hide()
|
||||||
navigation.presentingChannel = false
|
navigation.presentingChannel = false
|
||||||
|
|
||||||
@ -110,8 +111,14 @@ final class NavigationModel: ObservableObject {
|
|||||||
navigation.sidebarSectionChanged.toggle()
|
navigation.sidebarSectionChanged.toggle()
|
||||||
navigation.tabSelection = .recentlyOpened(recent.tag)
|
navigation.tabSelection = .recentlyOpened(recent.tag)
|
||||||
} else {
|
} else {
|
||||||
withAnimation(.linear(duration: 0.3)) {
|
var delay = 0.0
|
||||||
navigation.presentingChannel = true
|
#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
|
#endif
|
||||||
|
|
||||||
navigation.hideKeyboard()
|
navigation.hideKeyboard()
|
||||||
|
let presentingPlayer = player.presentingPlayer
|
||||||
|
player.hide()
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||||
recents.add(recent)
|
recents.add(recent)
|
||||||
@ -142,8 +151,14 @@ final class NavigationModel: ObservableObject {
|
|||||||
navigation.sidebarSectionChanged.toggle()
|
navigation.sidebarSectionChanged.toggle()
|
||||||
navigation.tabSelection = .recentlyOpened(recent.tag)
|
navigation.tabSelection = .recentlyOpened(recent.tag)
|
||||||
} else {
|
} else {
|
||||||
withAnimation(.linear(duration: 0.3)) {
|
var delay = 0.0
|
||||||
navigation.presentingPlaylist = true
|
#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,
|
navigation: NavigationModel,
|
||||||
search: SearchModel
|
search: SearchModel
|
||||||
) {
|
) {
|
||||||
player.hide()
|
|
||||||
navigation.presentingChannel = false
|
navigation.presentingChannel = false
|
||||||
navigation.presentingPlaylist = false
|
navigation.presentingPlaylist = false
|
||||||
navigation.tabSelection = .search
|
navigation.tabSelection = .search
|
||||||
|
|
||||||
navigation.hideKeyboard()
|
navigation.hideKeyboard()
|
||||||
|
|
||||||
|
let presentingPlayer = player.presentingPlayer
|
||||||
|
player.hide()
|
||||||
|
|
||||||
if let searchQuery = searchQuery {
|
if let searchQuery = searchQuery {
|
||||||
let recent = RecentItem(from: searchQuery)
|
let recent = RecentItem(from: searchQuery)
|
||||||
recents.add(recent)
|
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.queryText = searchQuery
|
||||||
search.changeQuery { query in query.query = searchQuery }
|
search.changeQuery { query in query.query = searchQuery }
|
||||||
}
|
}
|
||||||
|
@ -39,14 +39,14 @@ struct Video: Identifiable, Equatable, Hashable {
|
|||||||
init(
|
init(
|
||||||
id: String? = nil,
|
id: String? = nil,
|
||||||
videoID: String,
|
videoID: String,
|
||||||
title: String,
|
title: String = "",
|
||||||
author: String,
|
author: String = "",
|
||||||
length: TimeInterval,
|
length: TimeInterval = .zero,
|
||||||
published: String,
|
published: String = "",
|
||||||
views: Int,
|
views: Int = 0,
|
||||||
description: String? = nil,
|
description: String? = nil,
|
||||||
genre: String? = nil,
|
genre: String? = nil,
|
||||||
channel: Channel,
|
channel: Channel = .init(id: "", name: ""),
|
||||||
thumbnails: [Thumbnail] = [],
|
thumbnails: [Thumbnail] = [],
|
||||||
indexID: String? = nil,
|
indexID: String? = nil,
|
||||||
live: Bool = false,
|
live: Bool = false,
|
||||||
|
@ -140,7 +140,6 @@ extension Defaults.Keys {
|
|||||||
static let trendingCountry = Key<Country>("trendingCountry", default: .us)
|
static let trendingCountry = Key<Country>("trendingCountry", default: .us)
|
||||||
|
|
||||||
static let visibleSections = Key<Set<VisibleSection>>("visibleSections", default: [.favorites, .subscriptions, .trending, .playlists])
|
static let visibleSections = Key<Set<VisibleSection>>("visibleSections", default: [.favorites, .subscriptions, .trending, .playlists])
|
||||||
static let videoDetailsPage = Key<VideoDetails.DetailsPage>("videoDetailsPage", default: .info)
|
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
static let honorSystemOrientationLock = Key<Bool>("honorSystemOrientationLock", default: true)
|
static let honorSystemOrientationLock = Key<Bool>("honorSystemOrientationLock", default: true)
|
||||||
|
@ -101,6 +101,8 @@ struct OpenURLHandler {
|
|||||||
Windows.main.open()
|
Windows.main.open()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
player.videoBeingOpened = Video(videoID: id)
|
||||||
|
|
||||||
player.playerAPI.video(id)
|
player.playerAPI.video(id)
|
||||||
.load()
|
.load()
|
||||||
.onSuccess { response in
|
.onSuccess { response in
|
||||||
|
@ -102,8 +102,6 @@ struct PlayerControls: View {
|
|||||||
if model.presentingDetailsOverlay {
|
if model.presentingDetailsOverlay {
|
||||||
VideoDetailsOverlay()
|
VideoDetailsOverlay()
|
||||||
.frame(maxWidth: detailsWidth, maxHeight: detailsHeight)
|
.frame(maxWidth: detailsWidth, maxHeight: detailsHeight)
|
||||||
.modifier(ControlBackgroundModifier())
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ struct VideoDetailsOverlay: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VideoDetails(sidebarQueue: false, fullScreen: fullScreenBinding)
|
VideoDetails(sidebarQueue: false, fullScreen: fullScreenBinding)
|
||||||
|
.modifier(ControlBackgroundModifier())
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullScreenBinding: Binding<Bool> {
|
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()
|
@StateObject private var page: Page = .first()
|
||||||
|
|
||||||
@Environment(\.navigationStyle) private var navigationStyle
|
@Environment(\.navigationStyle) private var navigationStyle
|
||||||
|
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||||
|
|
||||||
@EnvironmentObject<AccountsModel> private var accounts
|
@EnvironmentObject<AccountsModel> private var accounts
|
||||||
@EnvironmentObject<CommentsModel> private var comments
|
@EnvironmentObject<CommentsModel> private var comments
|
||||||
@ -41,8 +42,6 @@ struct VideoDetails: View {
|
|||||||
@EnvironmentObject<RecentsModel> private var recents
|
@EnvironmentObject<RecentsModel> private var recents
|
||||||
@EnvironmentObject<SubscriptionsModel> private var subscriptions
|
@EnvironmentObject<SubscriptionsModel> private var subscriptions
|
||||||
|
|
||||||
@Default(.videoDetailsPage) private var videoDetailsPage
|
|
||||||
@Default(.showKeywords) private var showKeywords
|
|
||||||
@Default(.playerDetailsPageButtonLabelStyle) private var playerDetailsPageButtonLabelStyle
|
@Default(.playerDetailsPageButtonLabelStyle) private var playerDetailsPageButtonLabelStyle
|
||||||
|
|
||||||
var currentPage: DetailsPage {
|
var currentPage: DetailsPage {
|
||||||
@ -92,12 +91,10 @@ struct VideoDetails: View {
|
|||||||
if pageIndex == DetailsPage.comments.index {
|
if pageIndex == DetailsPage.comments.index {
|
||||||
comments.load()
|
comments.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
videoDetailsPage = DetailsPage.allCases.first { $0.index == pageIndex } ?? .info
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
page.update(.new(index: videoDetailsPage.index))
|
page.update(.moveToFirst)
|
||||||
|
|
||||||
guard video != nil, accounts.app.supportsSubscriptions else {
|
guard video != nil, accounts.app.supportsSubscriptions else {
|
||||||
subscribed = false
|
subscribed = false
|
||||||
@ -114,11 +111,20 @@ struct VideoDetails: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
|
.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 {
|
var publishedDateSection: some View {
|
||||||
Group {
|
Group {
|
||||||
if let video = player.currentVideo {
|
if let video = video {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
if let published = video.publishedDate {
|
if let published = video.publishedDate {
|
||||||
Text(published)
|
Text(published)
|
||||||
@ -144,7 +150,6 @@ struct VideoDetails: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
page.update(.new(index: destination.index))
|
page.update(.new(index: destination.index))
|
||||||
pageChangeAction?()
|
pageChangeAction?()
|
||||||
videoDetailsPage = destination
|
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
@ -199,10 +204,12 @@ struct VideoDetails: View {
|
|||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@State private var detailsSize = CGSize.zero
|
||||||
|
|
||||||
var detailsPage: some View {
|
var detailsPage: some View {
|
||||||
Group {
|
Group {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
if let video = player.currentVideo {
|
if let video = video {
|
||||||
VStack(spacing: 6) {
|
VStack(spacing: 6) {
|
||||||
videoProperties
|
videoProperties
|
||||||
|
|
||||||
@ -218,47 +225,13 @@ struct VideoDetails: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.redacted(reason: .placeholder)
|
.redacted(reason: .placeholder)
|
||||||
} else if let description = video.description {
|
} else if video.description != nil, !video.description!.isEmpty {
|
||||||
Group {
|
VideoDescription(video: video, detailsSize: detailsSize)
|
||||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
.padding(.bottom, fullScreenLayout ? 10 : SafeArea.insets.bottom)
|
||||||
Text(description)
|
|
||||||
#if !os(tvOS)
|
|
||||||
.textSelection(.enabled)
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
Text(description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.font(.system(size: 14))
|
|
||||||
.lineSpacing(3)
|
|
||||||
} else {
|
} else {
|
||||||
Text("No description")
|
Text("No description")
|
||||||
.foregroundColor(.secondary)
|
.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 {
|
@ViewBuilder var videoProperties: some View {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
publishedDateSection
|
publishedDateSection
|
||||||
@ -319,14 +300,6 @@ struct VideoDetails: View {
|
|||||||
|
|
||||||
.frame(maxWidth: 100)
|
.frame(maxWidth: 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
var showScrollIndicators: Bool {
|
|
||||||
#if os(macOS)
|
|
||||||
false
|
|
||||||
#else
|
|
||||||
true
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VideoDetails_Previews: PreviewProvider {
|
struct VideoDetails_Previews: PreviewProvider {
|
||||||
|
@ -303,6 +303,11 @@ struct VideoPlayerView: View {
|
|||||||
playerSize: player.playerSize,
|
playerSize: player.playerSize,
|
||||||
fullScreen: fullScreenDetails
|
fullScreen: fullScreenDetails
|
||||||
))
|
))
|
||||||
|
.onDisappear {
|
||||||
|
if player.presentingPlayer {
|
||||||
|
player.setNeedsDrawing(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -528,6 +528,7 @@
|
|||||||
379775932689365600DD52A8 /* Array+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379775922689365600DD52A8 /* Array+Next.swift */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
379B0253287A1CDF001015B5 /* OrientationTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379B0252287A1CDF001015B5 /* OrientationTracker.swift */; };
|
||||||
379F141F289ECE7F00DE48B5 /* QualitySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379F141E289ECE7F00DE48B5 /* QualitySettings.swift */; };
|
379F141F289ECE7F00DE48B5 /* QualitySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379F141E289ECE7F00DE48B5 /* QualitySettings.swift */; };
|
||||||
379F1420289ECE7F00DE48B5 /* 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 */; };
|
37CF8B8428535E4F00B71E37 /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = 37CF8B8328535E4F00B71E37 /* SDWebImage */; };
|
||||||
37CF8B8628535E5A00B71E37 /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = 37CF8B8528535E5A00B71E37 /* SDWebImage */; };
|
37CF8B8628535E5A00B71E37 /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = 37CF8B8528535E5A00B71E37 /* SDWebImage */; };
|
||||||
37CF8B8828535E6300B71E37 /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = 37CF8B8728535E6300B71E37 /* 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 */; };
|
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 */; };
|
37D4B0E32671614900C925CA /* Tests_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0E22671614900C925CA /* Tests_macOS.swift */; };
|
||||||
37D4B0E42671614900C925CA /* YatteeApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C22671614700C925CA /* YatteeApp.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
37D4B0C42671614800C925CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
@ -1287,6 +1292,7 @@
|
|||||||
37C2212B27ADA43700305B41 /* VideoToolbox.framework in Frameworks */,
|
37C2212B27ADA43700305B41 /* VideoToolbox.framework in Frameworks */,
|
||||||
3736A214286BB72300C9E5EE /* libswscale.xcframework in Frameworks */,
|
3736A214286BB72300C9E5EE /* libswscale.xcframework in Frameworks */,
|
||||||
3736A216286BB72300C9E5EE /* libavfilter.xcframework in Frameworks */,
|
3736A216286BB72300C9E5EE /* libavfilter.xcframework in Frameworks */,
|
||||||
|
3799AC0928B03CED001376F9 /* ActiveLabel in Frameworks */,
|
||||||
3736A218286BB72300C9E5EE /* libharfbuzz.xcframework in Frameworks */,
|
3736A218286BB72300C9E5EE /* libharfbuzz.xcframework in Frameworks */,
|
||||||
3736A206286BB72300C9E5EE /* libfreetype.xcframework in Frameworks */,
|
3736A206286BB72300C9E5EE /* libfreetype.xcframework in Frameworks */,
|
||||||
37C2212927ADA41400305B41 /* CoreMedia.framework in Frameworks */,
|
37C2212927ADA41400305B41 /* CoreMedia.framework in Frameworks */,
|
||||||
@ -1539,6 +1545,7 @@
|
|||||||
3795593527B08538007FF8F4 /* StreamControl.swift */,
|
3795593527B08538007FF8F4 /* StreamControl.swift */,
|
||||||
37B81AFE26D2CA3700675966 /* VideoDetails.swift */,
|
37B81AFE26D2CA3700675966 /* VideoDetails.swift */,
|
||||||
37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */,
|
37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */,
|
||||||
|
37CFB48428AFE2510070024C /* VideoDescription.swift */,
|
||||||
37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */,
|
37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */,
|
||||||
37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */,
|
37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */,
|
||||||
37F9619A27BD89E000058149 /* TapRecognizerViewModifier.swift */,
|
37F9619A27BD89E000058149 /* TapRecognizerViewModifier.swift */,
|
||||||
@ -2270,6 +2277,7 @@
|
|||||||
37A5DBC3285DFF5400CA4DD1 /* SwiftUIPager */,
|
37A5DBC3285DFF5400CA4DD1 /* SwiftUIPager */,
|
||||||
372AA40F286D067B0000B1DC /* Repeat */,
|
372AA40F286D067B0000B1DC /* Repeat */,
|
||||||
37EE6DC428A305AD00BFD632 /* Reachability */,
|
37EE6DC428A305AD00BFD632 /* Reachability */,
|
||||||
|
3799AC0828B03CED001376F9 /* ActiveLabel */,
|
||||||
);
|
);
|
||||||
productName = "Yattee (iOS)";
|
productName = "Yattee (iOS)";
|
||||||
productReference = 37D4B0C92671614900C925CA /* Yattee.app */;
|
productReference = 37D4B0C92671614900C925CA /* Yattee.app */;
|
||||||
@ -2480,6 +2488,7 @@
|
|||||||
37A5DBC2285DFF5400CA4DD1 /* XCRemoteSwiftPackageReference "SwiftUIPager" */,
|
37A5DBC2285DFF5400CA4DD1 /* XCRemoteSwiftPackageReference "SwiftUIPager" */,
|
||||||
372AA40E286D067B0000B1DC /* XCRemoteSwiftPackageReference "Repeat" */,
|
372AA40E286D067B0000B1DC /* XCRemoteSwiftPackageReference "Repeat" */,
|
||||||
37EE6DC328A305AD00BFD632 /* XCRemoteSwiftPackageReference "Reachability" */,
|
37EE6DC328A305AD00BFD632 /* XCRemoteSwiftPackageReference "Reachability" */,
|
||||||
|
3799AC0728B03CEC001376F9 /* XCRemoteSwiftPackageReference "ActiveLabel.swift" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 37D4B0CA2671614900C925CA /* Products */;
|
productRefGroup = 37D4B0CA2671614900C925CA /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@ -2860,6 +2869,7 @@
|
|||||||
37B17DA2268A1F8A006AEE9B /* VideoContextMenuView.swift in Sources */,
|
37B17DA2268A1F8A006AEE9B /* VideoContextMenuView.swift in Sources */,
|
||||||
3784B23B272894DA00B09468 /* ShareSheet.swift in Sources */,
|
3784B23B272894DA00B09468 /* ShareSheet.swift in Sources */,
|
||||||
379775932689365600DD52A8 /* Array+Next.swift in Sources */,
|
379775932689365600DD52A8 /* Array+Next.swift in Sources */,
|
||||||
|
37CFB48528AFE2510070024C /* VideoDescription.swift in Sources */,
|
||||||
37B81AFC26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */,
|
37B81AFC26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */,
|
||||||
37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
||||||
37BA794F26DC3E0E002A0235 /* Int+Format.swift in Sources */,
|
37BA794F26DC3E0E002A0235 /* Int+Format.swift in Sources */,
|
||||||
@ -3124,6 +3134,7 @@
|
|||||||
37C3A252272366440087A57A /* ChannelPlaylistView.swift in Sources */,
|
37C3A252272366440087A57A /* ChannelPlaylistView.swift in Sources */,
|
||||||
373CFADC269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
373CFADC269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
||||||
37C0697B2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */,
|
37C0697B2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */,
|
||||||
|
37CFB48628AFE2510070024C /* VideoDescription.swift in Sources */,
|
||||||
37C3A24A27235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */,
|
37C3A24A27235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */,
|
||||||
377ABC41286E4AD5009C986F /* InstancesManifest.swift in Sources */,
|
377ABC41286E4AD5009C986F /* InstancesManifest.swift in Sources */,
|
||||||
373CFAF02697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
373CFAF02697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||||
@ -3236,6 +3247,7 @@
|
|||||||
37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */,
|
37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */,
|
||||||
37E70925271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
37E70925271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
||||||
376BE50D27349108009AD608 /* BrowsingSettings.swift in Sources */,
|
376BE50D27349108009AD608 /* BrowsingSettings.swift in Sources */,
|
||||||
|
37CFB48728AFE2510070024C /* VideoDescription.swift in Sources */,
|
||||||
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
|
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
|
||||||
378AE93E274EDFB4006A4EE1 /* Tint+Backport.swift in Sources */,
|
378AE93E274EDFB4006A4EE1 /* Tint+Backport.swift in Sources */,
|
||||||
37FFC442272734C3009FFD26 /* Throttle.swift in Sources */,
|
37FFC442272734C3009FFD26 /* Throttle.swift in Sources */,
|
||||||
@ -4304,6 +4316,14 @@
|
|||||||
minimumVersion = 1.5.0;
|
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" */ = {
|
37A5DBC2285DFF5400CA4DD1 /* XCRemoteSwiftPackageReference "SwiftUIPager" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/fermoya/SwiftUIPager.git";
|
repositoryURL = "https://github.com/fermoya/SwiftUIPager.git";
|
||||||
@ -4502,6 +4522,11 @@
|
|||||||
package = 3797757B268922D100DD52A8 /* XCRemoteSwiftPackageReference "siesta" */;
|
package = 3797757B268922D100DD52A8 /* XCRemoteSwiftPackageReference "siesta" */;
|
||||||
productName = Siesta;
|
productName = Siesta;
|
||||||
};
|
};
|
||||||
|
3799AC0828B03CED001376F9 /* ActiveLabel */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 3799AC0728B03CEC001376F9 /* XCRemoteSwiftPackageReference "ActiveLabel.swift" */;
|
||||||
|
productName = ActiveLabel;
|
||||||
|
};
|
||||||
37A5DBC3285DFF5400CA4DD1 /* SwiftUIPager */ = {
|
37A5DBC3285DFF5400CA4DD1 /* SwiftUIPager */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 37A5DBC2285DFF5400CA4DD1 /* XCRemoteSwiftPackageReference "SwiftUIPager" */;
|
package = 37A5DBC2285DFF5400CA4DD1 /* XCRemoteSwiftPackageReference "SwiftUIPager" */;
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
{
|
{
|
||||||
"pins" : [
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "activelabel.swift",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/yattee/ActiveLabel.swift.git",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "feature/timestamp",
|
||||||
|
"revision" : "2a88b80e44f84aa614032466039307c65815b2f3"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "alamofire",
|
"identity" : "alamofire",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
Loading…
Reference in New Issue
Block a user