Compare commits

...

19 Commits

Author SHA1 Message Date
Arkadiusz Fal
1380036c44 Bump build and version number 2022-03-27 22:02:07 +02:00
Arkadiusz Fal
c893e5dc38 Fix menu commands 2022-03-27 22:02:07 +02:00
Arkadiusz Fal
8b4838dca5 Fix placeholders on tvOS 2022-03-27 20:31:56 +02:00
Arkadiusz Fal
1c520831d1 Improve placeholders 2022-03-27 20:27:59 +02:00
Arkadiusz Fal
8770bfb56d Fix #87 2022-03-27 13:26:38 +02:00
Arkadiusz Fal
ae4796a4c5 Add placeholders 2022-03-27 13:26:38 +02:00
Arkadiusz Fal
70b55ec2b2 Further subscribe buttons improvements 2022-03-26 19:01:38 +01:00
Arkadiusz Fal
c14a4a153d Fix #72 2022-03-26 15:22:29 +01:00
Arkadiusz Fal
c8fa972a61 Hide player on video end only on tvOS 2022-03-26 15:12:06 +01:00
Arkadiusz Fal
cc7bb83e74 Fix #84 2022-03-26 14:37:55 +01:00
Arkadiusz Fal
6a65123876 Hide subscribe button when not logged in 2022-03-26 14:07:00 +01:00
Arkadiusz Fal
aa42551c7c Fix #81 2022-03-26 13:50:01 +01:00
Arkadiusz Fal
9d8a2607ab Fix parsing subscriptions published date 2022-03-24 14:13:51 +01:00
Arkadiusz Fal
b4a0835a43 Fix #80 2022-03-24 14:04:31 +01:00
Arkadiusz Fal
066e048022 Add Defaults workaround 2022-03-24 14:03:38 +01:00
Arkadiusz Fal
d825cd8b20 Update README 2022-03-20 23:29:26 +01:00
Arkadiusz Fal
bb988764b4 Bump version number 2022-02-26 11:10:32 +01:00
Arkadiusz Fal
f7789c73d5 Fix opening playlists when recents is not saved (fix #57) 2022-02-26 11:10:29 +01:00
Ryan Stentz
1085bf0e9a fix issue #57 2022-02-26 11:07:50 +01:00
26 changed files with 407 additions and 196 deletions

View File

@@ -10,7 +10,7 @@ extension Thumbnail {
} }
private static var fixturesHost: String { private static var fixturesHost: String {
"https://invidious.home.arekf.net" "https://invidious.snopyta.org"
} }
private static func fixtureUrl(videoId: String, quality: Thumbnail.Quality) -> URL { private static func fixtureUrl(videoId: String, quality: Thumbnail.Quality) -> URL {

View File

@@ -1,13 +1,16 @@
import Foundation import Foundation
extension Video { extension Video {
static var fixtureID: Video.ID {
"FIXTURE"
}
static var fixture: Video { static var fixture: Video {
let id = "D2sxamzaHkM"
let thumbnailURL = "https://yt3.ggpht.com/ytc/AKedOLR-pT_JEsz_hcaA4Gjx8DHcqJ8mS42aTRqcVy6P7w=s88-c-k-c0x00ffffff-no-rj-mo" let thumbnailURL = "https://yt3.ggpht.com/ytc/AKedOLR-pT_JEsz_hcaA4Gjx8DHcqJ8mS42aTRqcVy6P7w=s88-c-k-c0x00ffffff-no-rj-mo"
return Video( return Video(
videoID: UUID().uuidString, videoID: fixtureID,
title: "Relaxing Piano Music that will make you feel amazingly good", title: "Relaxing Piano Music to feel good",
author: "Fancy Videotuber", author: "Fancy Videotuber",
length: 582, length: 582,
published: "7 years ago", published: "7 years ago",
@@ -21,7 +24,7 @@ extension Video {
subscriptionsCount: 2300, subscriptionsCount: 2300,
videos: [] videos: []
), ),
thumbnails: Thumbnail.fixturesForAllQualities(videoId: id), thumbnails: [],
live: false, live: false,
upcoming: false, upcoming: false,
publishedAt: Date(), publishedAt: Date(),

View File

@@ -232,6 +232,8 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
if let channel = extractChannel(from: content) { if let channel = extractChannel(from: content) {
return ContentItem(channel: channel) return ContentItem(channel: channel)
} }
default:
return nil
} }
return nil return nil
@@ -309,8 +311,12 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
let author = details["uploaderName"]?.stringValue ?? details["uploader"]!.stringValue let author = details["uploaderName"]?.stringValue ?? details["uploader"]!.stringValue
let authorThumbnailURL = details["avatarUrl"]?.url ?? details["uploaderAvatar"]?.url ?? details["avatar"]?.url let authorThumbnailURL = details["avatarUrl"]?.url ?? details["uploaderAvatar"]?.url ?? details["avatar"]?.url
let published = (details["uploadedDate"] ?? details["uploadDate"])?.stringValue ?? let uploaded = details["uploaded"]?.doubleValue
(details["uploaded"]!.double! / 1000).formattedAsRelativeTime()! var published = uploaded.isNil ? nil : (uploaded! / 1000).formattedAsRelativeTime()
if published.isNil {
published = (details["uploadedDate"] ?? details["uploadDate"])?.stringValue ?? ""
}
let live = details["livestream"]?.boolValue ?? (details["duration"]?.intValue == -1) let live = details["livestream"]?.boolValue ?? (details["duration"]?.intValue == -1)
return Video( return Video(
@@ -318,7 +324,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
title: details["title"]!.stringValue, title: details["title"]!.stringValue,
author: author, author: author,
length: details["duration"]!.doubleValue, length: details["duration"]!.doubleValue,
published: published, published: published!,
views: details["views"]!.intValue, views: details["views"]!.intValue,
description: extractDescription(from: content), description: extractDescription(from: content),
channel: Channel(id: channelId, name: author, thumbnailURL: authorThumbnailURL), channel: Channel(id: channelId, name: author, thumbnailURL: authorThumbnailURL),

View File

@@ -74,6 +74,8 @@ extension VideosAPI {
case .playlist: case .playlist:
urlComponents.path = "/playlist" urlComponents.path = "/playlist"
queryItems.append(.init(name: "list", value: item.playlist.id)) queryItems.append(.init(name: "list", value: item.playlist.id))
default:
return nil
} }
if !time.isNil, time!.seconds.isFinite { if !time.isNil, time!.seconds.isFinite {

View File

@@ -2,7 +2,7 @@ import Foundation
struct ContentItem: Identifiable { struct ContentItem: Identifiable {
enum ContentType: String { enum ContentType: String {
case video, playlist, channel case video, playlist, channel, placeholder
private var sortOrder: Int { private var sortOrder: Int {
switch self { switch self {
@@ -35,6 +35,6 @@ struct ContentItem: Identifiable {
} }
var contentType: ContentType { var contentType: ContentType {
video.isNil ? (channel.isNil ? .playlist : .channel) : .video video.isNil ? (channel.isNil ? (playlist.isNil ? .placeholder : .playlist) : .channel) : .video
} }
} }

View File

@@ -14,6 +14,31 @@ final class NavigationModel: ObservableObject {
case nowPlaying case nowPlaying
case search case search
var stringValue: String {
switch self {
case .favorites:
return "favorites"
case .subscriptions:
return "subscriptions"
case .popular:
return "popular"
case .trending:
return "trending"
case .playlists:
return "playlists"
case let .channel(string):
return "channel\(string)"
case let .playlist(string):
return "playlist\(string)"
case .recentlyOpened:
return "recentlyOpened"
case .search:
return "search"
default:
return ""
}
}
var playlistID: Playlist.ID? { var playlistID: Playlist.ID? {
if case let .playlist(id) = self { if case let .playlist(id) = self {
return id return id
@@ -141,6 +166,12 @@ final class NavigationModel: ObservableObject {
channelToUnsubscribe = channel channelToUnsubscribe = channel
presentingUnsubscribeAlert = channelToUnsubscribe != nil presentingUnsubscribeAlert = channelToUnsubscribe != nil
} }
func hideKeyboard() {
#if os(iOS)
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
#endif
}
} }
typealias TabSelection = NavigationModel.TabSelection typealias TabSelection = NavigationModel.TabSelection

View File

@@ -556,8 +556,6 @@ final class PlayerModel: ObservableObject {
controller?.playerView.dismiss(animated: false) { [weak self] in controller?.playerView.dismiss(animated: false) { [weak self] in
self?.controller?.dismiss(animated: true) self?.controller?.dismiss(animated: true)
} }
#else
hide()
#endif #endif
} else { } else {
advanceToNextItem() advanceToNextItem()

View File

@@ -16,7 +16,7 @@ final class RecentsModel: ObservableObject {
if !saveRecents { if !saveRecents {
clear() clear()
if item.type != .channel { if item.type == .query {
return return
} }
} }

View File

@@ -19,8 +19,13 @@
* Fullscreen playback, Picture in Picture and AirPlay support * Fullscreen playback, Picture in Picture and AirPlay support
* Stream quality selection * Stream quality selection
### Features in alpha testing for iOS and macOS
* New player component with custom controls, gestures and support for 4K playback
You can leave your feedback in [discussion on v1.4 release](https://github.com/yattee/yattee/discussions/75).
### Availability ### Availability
| Feature | Invidious | Piped | || Invidious | Piped |
| - | - | - | | - | - | - |
| User Accounts | ✅ | ✅ | | User Accounts | ✅ | ✅ |
| Subscriptions | ✅ | ✅ | | Subscriptions | ✅ | ✅ |

View File

@@ -0,0 +1,38 @@
import Defaults
import Foundation
extension Defaults.Serializable where Self: Codable {
static var bridge: Defaults.TopLevelCodableBridge<Self> { Defaults.TopLevelCodableBridge() }
}
extension Defaults.Serializable where Self: Codable & NSSecureCoding {
static var bridge: Defaults.CodableNSSecureCodingBridge<Self> { Defaults.CodableNSSecureCodingBridge() }
}
extension Defaults.Serializable where Self: Codable & NSSecureCoding & Defaults.PreferNSSecureCoding {
static var bridge: Defaults.NSSecureCodingBridge<Self> { Defaults.NSSecureCodingBridge() }
}
extension Defaults.Serializable where Self: Codable & RawRepresentable {
static var bridge: Defaults.RawRepresentableCodableBridge<Self> { Defaults.RawRepresentableCodableBridge() }
}
extension Defaults.Serializable where Self: Codable & RawRepresentable & Defaults.PreferRawRepresentable {
static var bridge: Defaults.RawRepresentableBridge<Self> { Defaults.RawRepresentableBridge() }
}
extension Defaults.Serializable where Self: RawRepresentable {
static var bridge: Defaults.RawRepresentableBridge<Self> { Defaults.RawRepresentableBridge() }
}
extension Defaults.Serializable where Self: NSSecureCoding {
static var bridge: Defaults.NSSecureCodingBridge<Self> { Defaults.NSSecureCodingBridge() }
}
extension Defaults.CollectionSerializable where Element: Defaults.Serializable {
static var bridge: Defaults.CollectionBridge<Self> { Defaults.CollectionBridge() }
}
extension Defaults.SetAlgebraSerializable where Element: Defaults.Serializable & Hashable {
static var bridge: Defaults.SetAlgebraBridge<Self> { Defaults.SetAlgebraBridge() }
}

View File

@@ -11,10 +11,18 @@ struct DropFavorite: DropDelegate {
return return
} }
let from = favorites.firstIndex(of: current!)! guard let current = current else {
let to = favorites.firstIndex(of: item)! return
}
guard favorites[to].id != current!.id else { let from = favorites.firstIndex(of: current)
let to = favorites.firstIndex(of: item)
guard let from = from, let to = to else {
return
}
guard favorites[to].id != current.id else {
return return
} }

View File

@@ -12,29 +12,29 @@ struct MenuCommands: Commands {
private var navigationMenu: some Commands { private var navigationMenu: some Commands {
CommandGroup(before: .windowSize) { CommandGroup(before: .windowSize) {
Button("Favorites") { Button("Favorites") {
model.navigation?.tabSelection = .favorites setTabSelection(.favorites)
} }
.keyboardShortcut("1") .keyboardShortcut("1")
Button("Subscriptions") { Button("Subscriptions") {
model.navigation?.tabSelection = .subscriptions setTabSelection(.subscriptions)
} }
.disabled(subscriptionsDisabled) .disabled(subscriptionsDisabled)
.keyboardShortcut("2") .keyboardShortcut("2")
Button("Popular") { Button("Popular") {
model.navigation?.tabSelection = .popular setTabSelection(.popular)
} }
.disabled(!(model.accounts?.app.supportsPopular ?? false)) .disabled(!(model.accounts?.app.supportsPopular ?? false))
.keyboardShortcut("3") .keyboardShortcut("3")
Button("Trending") { Button("Trending") {
model.navigation?.tabSelection = .trending setTabSelection(.trending)
} }
.keyboardShortcut("4") .keyboardShortcut("4")
Button("Search") { Button("Search") {
model.navigation?.tabSelection = .search setTabSelection(.search)
} }
.keyboardShortcut("f") .keyboardShortcut("f")
@@ -42,6 +42,15 @@ struct MenuCommands: Commands {
} }
} }
private func setTabSelection(_ tabSelection: NavigationModel.TabSelection) {
guard let navigation = model.navigation else {
return
}
navigation.sidebarSectionChanged.toggle()
navigation.tabSelection = tabSelection
}
private var subscriptionsDisabled: Bool { private var subscriptionsDisabled: Bool {
!( !(
(model.accounts?.app.supportsSubscriptions ?? false) && model.accounts?.signedIn ?? false (model.accounts?.app.supportsSubscriptions ?? false) && model.accounts?.signedIn ?? false

View File

@@ -45,6 +45,7 @@ struct Sidebar: View {
Label("Favorites", systemImage: "heart") Label("Favorites", systemImage: "heart")
.accessibility(label: Text("Favorites")) .accessibility(label: Text("Favorites"))
} }
.id("favorites")
} }
if visibleSections.contains(.subscriptions), if visibleSections.contains(.subscriptions),
accounts.app.supportsSubscriptions && accounts.signedIn accounts.app.supportsSubscriptions && accounts.signedIn
@@ -53,6 +54,7 @@ struct Sidebar: View {
Label("Subscriptions", systemImage: "star.circle") Label("Subscriptions", systemImage: "star.circle")
.accessibility(label: Text("Subscriptions")) .accessibility(label: Text("Subscriptions"))
} }
.id("subscriptions")
} }
if visibleSections.contains(.popular), accounts.app.supportsPopular { if visibleSections.contains(.popular), accounts.app.supportsPopular {
@@ -60,6 +62,7 @@ struct Sidebar: View {
Label("Popular", systemImage: "arrow.up.right.circle") Label("Popular", systemImage: "arrow.up.right.circle")
.accessibility(label: Text("Popular")) .accessibility(label: Text("Popular"))
} }
.id("popular")
} }
if visibleSections.contains(.trending) { if visibleSections.contains(.trending) {
@@ -67,12 +70,14 @@ struct Sidebar: View {
Label("Trending", systemImage: "chart.bar") Label("Trending", systemImage: "chart.bar")
.accessibility(label: Text("Trending")) .accessibility(label: Text("Trending"))
} }
.id("trending")
} }
NavigationLink(destination: LazyView(SearchView()), tag: TabSelection.search, selection: $navigation.tabSelection) { NavigationLink(destination: LazyView(SearchView()), tag: TabSelection.search, selection: $navigation.tabSelection) {
Label("Search", systemImage: "magnifyingglass") Label("Search", systemImage: "magnifyingglass")
.accessibility(label: Text("Search")) .accessibility(label: Text("Search"))
} }
.id("search")
.keyboardShortcut("f") .keyboardShortcut("f")
} }
} }
@@ -80,8 +85,12 @@ struct Sidebar: View {
private func scrollScrollViewToItem(scrollView: ScrollViewProxy, for selection: TabSelection) { private func scrollScrollViewToItem(scrollView: ScrollViewProxy, for selection: TabSelection) {
if case .recentlyOpened = selection { if case .recentlyOpened = selection {
scrollView.scrollTo("recentlyOpened") scrollView.scrollTo("recentlyOpened")
return
} else if case let .playlist(id) = selection { } else if case let .playlist(id) = selection {
scrollView.scrollTo(id) scrollView.scrollTo(id)
return
} }
scrollView.scrollTo(selection.stringValue)
} }
} }

View File

@@ -12,6 +12,7 @@ struct VideoDetails: View {
@Binding var fullScreen: Bool @Binding var fullScreen: Bool
@State private var subscribed = false @State private var subscribed = false
@State private var subscriptionToggleButtonDisabled = false
@State private var presentingUnsubscribeAlert = false @State private var presentingUnsubscribeAlert = false
@State private var presentingAddToPlaylist = false @State private var presentingAddToPlaylist = false
@State private var presentingShareSheet = false @State private var presentingShareSheet = false
@@ -236,7 +237,7 @@ struct VideoDetails: View {
} }
} }
if accounts.app.supportsSubscriptions { if accounts.app.supportsSubscriptions, accounts.signedIn {
Spacer() Spacer()
Section { Section {
@@ -254,10 +255,13 @@ struct VideoDetails: View {
"Are you sure you want to unsubscribe from \(video!.channel.name)?" "Are you sure you want to unsubscribe from \(video!.channel.name)?"
), ),
primaryButton: .destructive(Text("Unsubscribe")) { primaryButton: .destructive(Text("Unsubscribe")) {
subscriptions.unsubscribe(video!.channel.id) subscriptionToggleButtonDisabled = true
withAnimation { subscriptions.unsubscribe(video!.channel.id) {
subscribed.toggle() withAnimation {
subscriptionToggleButtonDisabled = false
subscribed.toggle()
}
} }
}, },
secondaryButton: .cancel() secondaryButton: .cancel()
@@ -265,16 +269,20 @@ struct VideoDetails: View {
} }
} else { } else {
Button("Subscribe") { Button("Subscribe") {
subscriptions.subscribe(video!.channel.id) subscriptionToggleButtonDisabled = true
withAnimation { subscriptions.subscribe(video!.channel.id) {
subscribed.toggle() withAnimation {
subscriptionToggleButtonDisabled = false
subscribed.toggle()
}
} }
} }
.backport .backport
.tint(.blue) .tint(subscriptionToggleButtonDisabled ? .gray : .blue)
} }
} }
.disabled(subscriptionToggleButtonDisabled)
.font(.system(size: 13)) .font(.system(size: 13))
.buttonStyle(.borderless) .buttonStyle(.borderless)
} }

View File

@@ -29,7 +29,10 @@ struct SearchTextField: View {
.opacity(0.8) .opacity(0.8)
#endif #endif
TextField("Search...", text: $state.queryText) { TextField("Search...", text: $state.queryText) {
state.changeQuery { query in query.query = state.queryText } state.changeQuery { query in
query.query = state.queryText
navigation.hideKeyboard()
}
recents.addQuery(state.queryText, navigation: navigation) recents.addQuery(state.queryText, navigation: navigation)
} }
.onChange(of: state.queryText) { _ in .onChange(of: state.queryText) { _ in

View File

@@ -75,6 +75,7 @@ struct SearchSuggestions: View {
state.changeQuery { query in state.changeQuery { query in
query.query = state.queryText query.query = state.queryText
state.fieldIsFocused = false state.fieldIsFocused = false
navigation.hideKeyboard()
} }
recents.addQuery(state.queryText, navigation: navigation) recents.addQuery(state.queryText, navigation: navigation)

View File

@@ -234,7 +234,7 @@ struct SearchView: View {
} }
.edgesIgnoringSafeArea(.horizontal) .edgesIgnoringSafeArea(.horizontal)
#else #else
VerticalCells(items: items) VerticalCells(items: items, allowEmpty: state.query.isEmpty)
.environment(\.loadMoreContentHandler) { state.loadNextPage() } .environment(\.loadMoreContentHandler) { state.loadNextPage() }
#endif #endif
@@ -353,7 +353,7 @@ struct SearchView: View {
private var removeAllButton: some View { private var removeAllButton: some View {
Button { Button {
recents.clearQueries() recents.clear()
recentsChanged.toggle() recentsChanged.toggle()
} label: { } label: {
Label("Remove All", systemImage: "trash.fill") Label("Remove All", systemImage: "trash.fill")

View File

@@ -11,7 +11,7 @@ struct HorizontalCells: View {
var body: some View { var body: some View {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 20) { LazyHStack(spacing: 20) {
ForEach(items) { item in ForEach(contentItems) { item in
ContentItemView(item: item) ContentItemView(item: item)
.environment(\.horizontalCells, true) .environment(\.horizontalCells, true)
.onAppear { loadMoreContentItemsIfNeeded(current: item) } .onAppear { loadMoreContentItemsIfNeeded(current: item) }
@@ -36,6 +36,14 @@ struct HorizontalCells: View {
.edgesIgnoringSafeArea(.horizontal) .edgesIgnoringSafeArea(.horizontal)
} }
var contentItems: [ContentItem] {
items.isEmpty ? placeholders : items
}
var placeholders: [ContentItem] {
(0 ..< 9).map { _ in .init() }
}
func loadMoreContentItemsIfNeeded(current item: ContentItem) { func loadMoreContentItemsIfNeeded(current item: ContentItem) {
let thresholdIndex = items.index(items.endIndex, offsetBy: -5) let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex { if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex {

View File

@@ -9,11 +9,12 @@ struct VerticalCells: View {
@Environment(\.loadMoreContentHandler) private var loadMoreContentHandler @Environment(\.loadMoreContentHandler) private var loadMoreContentHandler
var items = [ContentItem]() var items = [ContentItem]()
var allowEmpty = false
var body: some View { var body: some View {
ScrollView(.vertical, showsIndicators: scrollViewShowsIndicators) { ScrollView(.vertical, showsIndicators: scrollViewShowsIndicators) {
LazyVGrid(columns: columns, alignment: .center) { LazyVGrid(columns: columns, alignment: .center) {
ForEach(items.sorted { $0 < $1 }) { item in ForEach(contentItems) { item in
ContentItemView(item: item) ContentItemView(item: item)
.onAppear { loadMoreContentItemsIfNeeded(current: item) } .onAppear { loadMoreContentItemsIfNeeded(current: item) }
} }
@@ -27,6 +28,14 @@ struct VerticalCells: View {
#endif #endif
} }
var contentItems: [ContentItem] {
items.isEmpty ? (allowEmpty ? items : placeholders) : items.sorted { $0 < $1 }
}
var placeholders: [ContentItem] {
(0 ..< 9).map { _ in .init() }
}
func loadMoreContentItemsIfNeeded(current item: ContentItem) { func loadMoreContentItemsIfNeeded(current item: ContentItem) {
let thresholdIndex = items.index(items.endIndex, offsetBy: -5) let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex { if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex {
@@ -36,7 +45,7 @@ struct VerticalCells: View {
var columns: [GridItem] { var columns: [GridItem] {
#if os(tvOS) #if os(tvOS)
items.count < 3 ? Array(repeating: GridItem(.fixed(500)), count: [items.count, 1].max()!) : adaptiveItem contentItems.count < 3 ? Array(repeating: GridItem(.fixed(500)), count: [contentItems.count, 1].max()!) : adaptiveItem
#else #else
adaptiveItem adaptiveItem
#endif #endif

View File

@@ -6,6 +6,7 @@ struct VideoCell: View {
private var video: Video private var video: Video
@Environment(\.inNavigationView) private var inNavigationView @Environment(\.inNavigationView) private var inNavigationView
@Environment(\.navigationStyle) private var navigationStyle
#if os(iOS) #if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass @Environment(\.verticalSizeClass) private var verticalSizeClass
@@ -13,7 +14,9 @@ struct VideoCell: View {
#endif #endif
@EnvironmentObject<AccountsModel> private var accounts @EnvironmentObject<AccountsModel> private var accounts
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<PlayerModel> private var player @EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<ThumbnailsModel> private var thumbnails @EnvironmentObject<ThumbnailsModel> private var thumbnails
@Default(.channelOnThumbnail) private var channelOnThumbnail @Default(.channelOnThumbnail) private var channelOnThumbnail
@@ -59,6 +62,10 @@ struct VideoCell: View {
} }
private func playAction() { private func playAction() {
guard video.videoID != Video.fixtureID else {
return
}
if watchingNow { if watchingNow {
if !player.playingInPictureInPicture { if !player.playingInPictureInPicture {
player.show() player.show()
@@ -147,9 +154,7 @@ struct VideoCell: View {
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
if !channelOnThumbnail { if !channelOnThumbnail {
Text(video.channel.name) channelButton(badge: false)
.fontWeight(.semibold)
.foregroundColor(.secondary)
} }
if additionalDetailsAvailable { if additionalDetailsAvailable {
@@ -231,9 +236,7 @@ struct VideoCell: View {
.frame(minHeight: 40, alignment: .top) .frame(minHeight: 40, alignment: .top)
#endif #endif
if !channelOnThumbnail { if !channelOnThumbnail {
Text(video.channel.name) channelButton(badge: false)
.fontWeight(.semibold)
.foregroundColor(.secondary)
.padding(.top, 4) .padding(.top, 4)
.padding(.bottom, 6) .padding(.bottom, 6)
} }
@@ -289,6 +292,29 @@ struct VideoCell: View {
} }
} }
private func channelButton(badge: Bool = true) -> some View {
Button {
NavigationModel.openChannel(
video.channel,
player: player,
recents: recents,
navigation: navigation,
navigationStyle: navigationStyle
)
} label: {
if badge {
DetailBadge(text: video.author, style: .prominent)
.foregroundColor(.primary)
} else {
Text(video.channel.name)
.fontWeight(.semibold)
.foregroundColor(.secondary)
}
}
.buttonStyle(.plain)
.help("\(video.channel.name) Channel")
}
private var additionalDetailsAvailable: Bool { private var additionalDetailsAvailable: Bool {
video.publishedDate != nil || video.views != 0 || video.publishedDate != nil || video.views != 0 ||
(!timeOnThumbnail && !video.length.formattedAsPlaybackTime().isNil) (!timeOnThumbnail && !video.length.formattedAsPlaybackTime().isNil)
@@ -325,7 +351,7 @@ struct VideoCell: View {
Spacer() Spacer()
if channelOnThumbnail { if channelOnThumbnail {
DetailBadge(text: video.author, style: .prominent) channelButton()
} }
} }
#if os(tvOS) #if os(tvOS)
@@ -415,7 +441,7 @@ struct VideoCell: View {
stoppedAt.isFinite, stoppedAt.isFinite,
let stoppedAtFormatted = stoppedAt.formattedAsPlaybackTime() let stoppedAtFormatted = stoppedAt.formattedAsPlaybackTime()
{ {
if watch?.videoDuration ?? 0 > 0 { if (watch?.videoDuration ?? 0) > 0 {
videoTime = watch!.videoDuration.formattedAsPlaybackTime() ?? "?" videoTime = watch!.videoDuration.formattedAsPlaybackTime() ?? "?"
} }
return "\(stoppedAtFormatted) / \(videoTime)" return "\(stoppedAtFormatted) / \(videoTime)"

View File

@@ -6,6 +6,7 @@ struct ChannelVideosView: View {
@State private var presentingShareSheet = false @State private var presentingShareSheet = false
@State private var shareURL: URL? @State private var shareURL: URL?
@State private var subscriptionToggleButtonDisabled = false
@StateObject private var store = Store<Channel>() @StateObject private var store = Store<Channel>()
@@ -146,17 +147,25 @@ struct ChannelVideosView: View {
if accounts.app.supportsSubscriptions && accounts.signedIn { if accounts.app.supportsSubscriptions && accounts.signedIn {
if subscriptions.isSubscribing(channel.id) { if subscriptions.isSubscribing(channel.id) {
Button("Unsubscribe") { Button("Unsubscribe") {
navigation.presentUnsubscribeAlert(channel) subscriptionToggleButtonDisabled = true
subscriptions.unsubscribe(channel.id) {
subscriptionToggleButtonDisabled = false
}
} }
} else { } else {
Button("Subscribe") { Button("Subscribe") {
subscriptionToggleButtonDisabled = true
subscriptions.subscribe(channel.id) { subscriptions.subscribe(channel.id) {
subscriptionToggleButtonDisabled = false
navigation.sidebarSectionChanged.toggle() navigation.sidebarSectionChanged.toggle()
} }
} }
} }
} }
} }
.disabled(subscriptionToggleButtonDisabled)
} }
private var contentItem: ContentItem { private var contentItem: ContentItem {

View File

@@ -11,8 +11,10 @@ struct ContentItemView: View {
ChannelPlaylistCell(playlist: item.playlist) ChannelPlaylistCell(playlist: item.playlist)
case .channel: case .channel:
ChannelCell(channel: item.channel) ChannelCell(channel: item.channel)
default: case .video:
VideoCell(video: item.video) VideoCell(video: item.video)
default:
PlaceholderCell()
} }
} }
} }

View File

@@ -0,0 +1,16 @@
import Defaults
import SwiftUI
struct PlaceholderCell: View {
var body: some View {
VideoCell(video: .fixture)
.redacted(reason: .placeholder)
}
}
struct PlaceholderCell_Previews: PreviewProvider {
static var previews: some View {
PlaceholderCell()
.injectFixtureEnvironmentObjects()
}
}

View File

@@ -33,6 +33,12 @@ struct VideoContextMenuView: View {
} }
var body: some View { var body: some View {
if video.videoID != Video.fixtureID {
contextMenu
}
}
@ViewBuilder var contextMenu: some View {
if saveHistory { if saveHistory {
Section { Section {
if let watchedAtString = watchedAtString { if let watchedAtString = watchedAtString {

View File

@@ -514,6 +514,9 @@
37D526E02720AC4400ED2F5E /* VideosAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D526DD2720AC4400ED2F5E /* VideosAPI.swift */; }; 37D526E02720AC4400ED2F5E /* VideosAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D526DD2720AC4400ED2F5E /* VideosAPI.swift */; };
37D526E32720B4BE00ED2F5E /* View+SwipeGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D526E22720B4BE00ED2F5E /* View+SwipeGesture.swift */; }; 37D526E32720B4BE00ED2F5E /* View+SwipeGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D526E22720B4BE00ED2F5E /* View+SwipeGesture.swift */; };
37D526E42720B4BE00ED2F5E /* View+SwipeGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D526E22720B4BE00ED2F5E /* View+SwipeGesture.swift */; }; 37D526E42720B4BE00ED2F5E /* View+SwipeGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D526E22720B4BE00ED2F5E /* View+SwipeGesture.swift */; };
37D6F3A127ECA1FF006FE38B /* Defaults+Workaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D6F3A027ECA1FF006FE38B /* Defaults+Workaround.swift */; };
37D6F3A227ECA1FF006FE38B /* Defaults+Workaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D6F3A027ECA1FF006FE38B /* Defaults+Workaround.swift */; };
37D6F3A327ECA1FF006FE38B /* Defaults+Workaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D6F3A027ECA1FF006FE38B /* Defaults+Workaround.swift */; };
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; }; 37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
37DD87C8271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; }; 37DD87C8271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; }; 37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
@@ -587,6 +590,9 @@
37FD43E42704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; }; 37FD43E42704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; };
37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; }; 37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; };
37FD43F02704A9C00073EE42 /* RecentsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C194C626F6A9C8005D3B96 /* RecentsModel.swift */; }; 37FD43F02704A9C00073EE42 /* RecentsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C194C626F6A9C8005D3B96 /* RecentsModel.swift */; };
37FEF11327EFD8580033912F /* PlaceholderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FEF11227EFD8580033912F /* PlaceholderCell.swift */; };
37FEF11427EFD8580033912F /* PlaceholderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FEF11227EFD8580033912F /* PlaceholderCell.swift */; };
37FEF11527EFD8580033912F /* PlaceholderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FEF11227EFD8580033912F /* PlaceholderCell.swift */; };
37FFC440272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; }; 37FFC440272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
37FFC441272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; }; 37FFC441272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
37FFC442272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; }; 37FFC442272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
@@ -791,6 +797,7 @@
37D4B1AE26729DEB00C925CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 37D4B1AE26729DEB00C925CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
37D526DD2720AC4400ED2F5E /* VideosAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosAPI.swift; sourceTree = "<group>"; }; 37D526DD2720AC4400ED2F5E /* VideosAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosAPI.swift; sourceTree = "<group>"; };
37D526E22720B4BE00ED2F5E /* View+SwipeGesture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+SwipeGesture.swift"; sourceTree = "<group>"; }; 37D526E22720B4BE00ED2F5E /* View+SwipeGesture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+SwipeGesture.swift"; sourceTree = "<group>"; };
37D6F3A027ECA1FF006FE38B /* Defaults+Workaround.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Defaults+Workaround.swift"; sourceTree = "<group>"; };
37D9169A27388A81002B1BAA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; 37D9169A27388A81002B1BAA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = "<group>"; }; 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = "<group>"; };
37DD9DA22785BBC900539416 /* NoCommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCommentsView.swift; sourceTree = "<group>"; }; 37DD9DA22785BBC900539416 /* NoCommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCommentsView.swift; sourceTree = "<group>"; };
@@ -818,6 +825,7 @@
37FB285D272225E800A57617 /* ContentItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentItemView.swift; sourceTree = "<group>"; }; 37FB285D272225E800A57617 /* ContentItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentItemView.swift; sourceTree = "<group>"; };
37FD43DB270470B70073EE42 /* InstancesSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesSettings.swift; sourceTree = "<group>"; }; 37FD43DB270470B70073EE42 /* InstancesSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesSettings.swift; sourceTree = "<group>"; };
37FD43E22704847C0073EE42 /* View+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Fixtures.swift"; sourceTree = "<group>"; }; 37FD43E22704847C0073EE42 /* View+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Fixtures.swift"; sourceTree = "<group>"; };
37FEF11227EFD8580033912F /* PlaceholderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderCell.swift; sourceTree = "<group>"; };
37FFC43F272734C3009FFD26 /* Throttle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Throttle.swift; sourceTree = "<group>"; }; 37FFC43F272734C3009FFD26 /* Throttle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Throttle.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -1001,6 +1009,7 @@
37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */, 37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */,
37E70922271CD43000D34DDE /* WelcomeScreen.swift */, 37E70922271CD43000D34DDE /* WelcomeScreen.swift */,
3769C02D2779F18600DDB3EA /* PlaceholderProgressView.swift */, 3769C02D2779F18600DDB3EA /* PlaceholderProgressView.swift */,
37FEF11227EFD8580033912F /* PlaceholderCell.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1257,6 +1266,7 @@
371AAE2826CEC7D900901972 /* Views */, 371AAE2826CEC7D900901972 /* Views */,
375168D52700FAFF008F96A6 /* Debounce.swift */, 375168D52700FAFF008F96A6 /* Debounce.swift */,
372915E52687E3B900F5A35B /* Defaults.swift */, 372915E52687E3B900F5A35B /* Defaults.swift */,
37D6F3A027ECA1FF006FE38B /* Defaults+Workaround.swift */,
3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */, 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */,
3729037D2739E47400EA99F6 /* MenuCommands.swift */, 3729037D2739E47400EA99F6 /* MenuCommands.swift */,
37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */, 37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */,
@@ -2009,12 +2019,14 @@
37484C2526FC83E000287258 /* InstanceForm.swift in Sources */, 37484C2526FC83E000287258 /* InstanceForm.swift in Sources */,
37DD9DBD2785D60300539416 /* ScrollViewMatcher.swift in Sources */, 37DD9DBD2785D60300539416 /* ScrollViewMatcher.swift in Sources */,
37B767DB2677C3CA0098BAA8 /* PlayerModel.swift in Sources */, 37B767DB2677C3CA0098BAA8 /* PlayerModel.swift in Sources */,
37D6F3A127ECA1FF006FE38B /* Defaults+Workaround.swift in Sources */,
3788AC2726F6840700F6BAA9 /* FavoriteItemView.swift in Sources */, 3788AC2726F6840700F6BAA9 /* FavoriteItemView.swift in Sources */,
375DFB5826F9DA010013F468 /* InstancesModel.swift in Sources */, 375DFB5826F9DA010013F468 /* InstancesModel.swift in Sources */,
37DD9DC62785D63A00539416 /* UIResponder+Extensions.swift in Sources */, 37DD9DC62785D63A00539416 /* UIResponder+Extensions.swift in Sources */,
37C3A24927235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */, 37C3A24927235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */,
373CFACB26966264003CB2C6 /* SearchQuery.swift in Sources */, 373CFACB26966264003CB2C6 /* SearchQuery.swift in Sources */,
37141673267A8E10006CA35D /* Country.swift in Sources */, 37141673267A8E10006CA35D /* Country.swift in Sources */,
37FEF11327EFD8580033912F /* PlaceholderCell.swift in Sources */,
37B2631A2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */, 37B2631A2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */,
3748186E26A769D60084E870 /* DetailBadge.swift in Sources */, 3748186E26A769D60084E870 /* DetailBadge.swift in Sources */,
376BE50B27349108009AD608 /* BrowsingSettings.swift in Sources */, 376BE50B27349108009AD608 /* BrowsingSettings.swift in Sources */,
@@ -2099,6 +2111,7 @@
37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */, 37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */,
378AE944274EF00A006A4EE1 /* Color+Background.swift in Sources */, 378AE944274EF00A006A4EE1 /* Color+Background.swift in Sources */,
37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */, 37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
37D6F3A227ECA1FF006FE38B /* Defaults+Workaround.swift in Sources */,
37EAD870267B9ED100D9E01B /* Segment.swift in Sources */, 37EAD870267B9ED100D9E01B /* Segment.swift in Sources */,
3788AC2826F6840700F6BAA9 /* FavoriteItemView.swift in Sources */, 3788AC2826F6840700F6BAA9 /* FavoriteItemView.swift in Sources */,
378AE93A274EDFAF006A4EE1 /* Badge+Backport.swift in Sources */, 378AE93A274EDFAF006A4EE1 /* Badge+Backport.swift in Sources */,
@@ -2160,6 +2173,7 @@
3748186726A7627F0084E870 /* Video+Fixtures.swift in Sources */, 3748186726A7627F0084E870 /* Video+Fixtures.swift in Sources */,
3784B23E2728B85300B09468 /* ShareButton.swift in Sources */, 3784B23E2728B85300B09468 /* ShareButton.swift in Sources */,
37BE0BDA26A214630092E2DB /* PlayerViewController.swift in Sources */, 37BE0BDA26A214630092E2DB /* PlayerViewController.swift in Sources */,
37FEF11427EFD8580033912F /* PlaceholderCell.swift in Sources */,
37E64DD226D597EB00C71877 /* SubscriptionsModel.swift in Sources */, 37E64DD226D597EB00C71877 /* SubscriptionsModel.swift in Sources */,
374108D1272B11B2006C5CC8 /* PictureInPictureDelegate.swift in Sources */, 374108D1272B11B2006C5CC8 /* PictureInPictureDelegate.swift in Sources */,
37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */, 37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
@@ -2379,11 +2393,13 @@
37CEE4C32677B697005A1EFE /* Stream.swift in Sources */, 37CEE4C32677B697005A1EFE /* Stream.swift in Sources */,
37F64FE626FE70A60081B69E /* RedrawOnModifier.swift in Sources */, 37F64FE626FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
37B2631C2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */, 37B2631C2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */,
37D6F3A327ECA1FF006FE38B /* Defaults+Workaround.swift in Sources */,
37484C2B26FC83FF00287258 /* AccountForm.swift in Sources */, 37484C2B26FC83FF00287258 /* AccountForm.swift in Sources */,
37FB2860272225E800A57617 /* ContentItemView.swift in Sources */, 37FB2860272225E800A57617 /* ContentItemView.swift in Sources */,
374C053727242D9F009BDDBE /* SponsorBlockSettings.swift in Sources */, 374C053727242D9F009BDDBE /* SponsorBlockSettings.swift in Sources */,
37C069802725C8D400F7F6CB /* CMTime+DefaultTimescale.swift in Sources */, 37C069802725C8D400F7F6CB /* CMTime+DefaultTimescale.swift in Sources */,
3711404126B206A6005B3555 /* SearchModel.swift in Sources */, 3711404126B206A6005B3555 /* SearchModel.swift in Sources */,
37FEF11527EFD8580033912F /* PlaceholderCell.swift in Sources */,
37FD43F02704A9C00073EE42 /* RecentsModel.swift in Sources */, 37FD43F02704A9C00073EE42 /* RecentsModel.swift in Sources */,
379775952689365600DD52A8 /* Array+Next.swift in Sources */, 379775952689365600DD52A8 /* Array+Next.swift in Sources */,
3705B180267B4DFB00704544 /* TrendingCountry.swift in Sources */, 3705B180267B4DFB00704544 /* TrendingCountry.swift in Sources */,
@@ -2458,7 +2474,7 @@
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements"; CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -2471,7 +2487,7 @@
"@executable_path/../../../../Frameworks", "@executable_path/../../../../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-framework", "-framework",
SafariServices, SafariServices,
@@ -2492,7 +2508,7 @@
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements"; CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -2505,7 +2521,7 @@
"@executable_path/../../../../Frameworks", "@executable_path/../../../../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-framework", "-framework",
SafariServices, SafariServices,
@@ -2524,7 +2540,7 @@
buildSettings = { buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Open in Yattee/Info.plist"; INFOPLIST_FILE = "Open in Yattee/Info.plist";
@@ -2536,7 +2552,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-framework", "-framework",
SafariServices, SafariServices,
@@ -2556,7 +2572,7 @@
buildSettings = { buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Open in Yattee/Info.plist"; INFOPLIST_FILE = "Open in Yattee/Info.plist";
@@ -2568,7 +2584,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-framework", "-framework",
SafariServices, SafariServices,
@@ -2719,7 +2735,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -2735,7 +2751,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app; PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
PRODUCT_NAME = Yattee; PRODUCT_NAME = Yattee;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2751,7 +2767,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -2767,7 +2783,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app; PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
PRODUCT_NAME = Yattee; PRODUCT_NAME = Yattee;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2787,7 +2803,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -2802,7 +2818,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app; PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
PRODUCT_NAME = Yattee; PRODUCT_NAME = Yattee;
SDKROOT = macosx; SDKROOT = macosx;
@@ -2820,7 +2836,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -2835,7 +2851,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app; PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
PRODUCT_NAME = Yattee; PRODUCT_NAME = Yattee;
SDKROOT = macosx; SDKROOT = macosx;
@@ -2951,7 +2967,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -2966,7 +2982,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app; PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
PRODUCT_NAME = Yattee; PRODUCT_NAME = Yattee;
SDKROOT = appletvos; SDKROOT = appletvos;
@@ -2983,7 +2999,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -2998,7 +3014,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app; PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
PRODUCT_NAME = Yattee; PRODUCT_NAME = Yattee;
SDKROOT = appletvos; SDKROOT = appletvos;

View File

@@ -1,133 +1,131 @@
{ {
"object": { "pins" : [
"pins": [ {
{ "identity" : "alamofire",
"package": "Alamofire", "kind" : "remoteSourceControl",
"repositoryURL": "https://github.com/Alamofire/Alamofire.git", "location" : "https://github.com/Alamofire/Alamofire.git",
"state": { "state" : {
"branch": null, "revision" : "f82c23a8a7ef8dc1a49a8bfc6a96883e79121864",
"revision": "f82c23a8a7ef8dc1a49a8bfc6a96883e79121864", "version" : "5.5.0"
"version": "5.5.0"
}
},
{
"package": "Defaults",
"repositoryURL": "https://github.com/sindresorhus/Defaults",
"state": {
"branch": null,
"revision": "55f3302c3ab30a8760f10042d0ebc0a6907f865a",
"version": "6.1.0"
}
},
{
"package": "libwebp",
"repositoryURL": "https://github.com/SDWebImage/libwebp-Xcode.git",
"state": {
"branch": null,
"revision": "2b3b43faaef54d1b897482428428357b7f7cd08b",
"version": "1.2.1"
}
},
{
"package": "PINCache",
"repositoryURL": "https://github.com/pinterest/PINCache",
"state": {
"branch": "master",
"revision": "9ca06045b5aff12ee8c0ef5880aa8469c4896144",
"version": null
}
},
{
"package": "PINOperation",
"repositoryURL": "https://github.com/pinterest/PINOperation.git",
"state": {
"branch": null,
"revision": "44d8ca154a4e75a028a5548c31ff3a53b90cef15",
"version": "1.2.1"
}
},
{
"package": "SDWebImage",
"repositoryURL": "https://github.com/SDWebImage/SDWebImage.git",
"state": {
"branch": null,
"revision": "0fff0d7505b5306348263ea64fcc561253bbeb21",
"version": "5.12.2"
}
},
{
"package": "SDWebImagePINPlugin",
"repositoryURL": "https://github.com/SDWebImage/SDWebImagePINPlugin.git",
"state": {
"branch": null,
"revision": "bd73a4fb30352ec311303d811559c9c46df4caa4",
"version": "0.3.0"
}
},
{
"package": "SDWebImageSwiftUI",
"repositoryURL": "https://github.com/SDWebImage/SDWebImageSwiftUI.git",
"state": {
"branch": null,
"revision": "cd8625b7cf11a97698e180d28bb7d5d357196678",
"version": "2.0.2"
}
},
{
"package": "SDWebImageWebPCoder",
"repositoryURL": "https://github.com/SDWebImage/SDWebImageWebPCoder.git",
"state": {
"branch": null,
"revision": "95a6838df13bc08d8064cf7e048b787b6e52348d",
"version": "0.8.4"
}
},
{
"package": "Siesta",
"repositoryURL": "https://github.com/bustoutsolutions/siesta",
"state": {
"branch": null,
"revision": "43f34046ebb5beb6802200353c473af303bbc31e",
"version": "1.5.2"
}
},
{
"package": "Sparkle",
"repositoryURL": "https://github.com/sparkle-project/Sparkle",
"state": {
"branch": "2.x",
"revision": "71fc8d7b1182d24879edacefccb06151e99c34fe",
"version": null
}
},
{
"package": "swift-log",
"repositoryURL": "https://github.com/apple/swift-log.git",
"state": {
"branch": null,
"revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
"version": "1.4.2"
}
},
{
"package": "Introspect",
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git",
"state": {
"branch": null,
"revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2",
"version": "0.1.3"
}
},
{
"package": "SwiftyJSON",
"repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git",
"state": {
"branch": null,
"revision": "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07",
"version": "5.0.1"
}
} }
] },
}, {
"version": 1 "identity" : "defaults",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sindresorhus/Defaults",
"state" : {
"revision" : "119f654d44f7b90f00dc11f7dd1c94a36f12576b",
"version" : "6.2.1"
}
},
{
"identity" : "libwebp-xcode",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/libwebp-Xcode.git",
"state" : {
"revision" : "2b3b43faaef54d1b897482428428357b7f7cd08b",
"version" : "1.2.1"
}
},
{
"identity" : "pincache",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pinterest/PINCache",
"state" : {
"branch" : "master",
"revision" : "9ca06045b5aff12ee8c0ef5880aa8469c4896144"
}
},
{
"identity" : "pinoperation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pinterest/PINOperation.git",
"state" : {
"revision" : "44d8ca154a4e75a028a5548c31ff3a53b90cef15",
"version" : "1.2.1"
}
},
{
"identity" : "sdwebimage",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/SDWebImage.git",
"state" : {
"revision" : "2e63d0061da449ad0ed130768d05dceb1496de44",
"version" : "5.12.5"
}
},
{
"identity" : "sdwebimagepinplugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/SDWebImagePINPlugin.git",
"state" : {
"revision" : "bd73a4fb30352ec311303d811559c9c46df4caa4",
"version" : "0.3.0"
}
},
{
"identity" : "sdwebimageswiftui",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/SDWebImageSwiftUI.git",
"state" : {
"revision" : "cd8625b7cf11a97698e180d28bb7d5d357196678",
"version" : "2.0.2"
}
},
{
"identity" : "sdwebimagewebpcoder",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/SDWebImageWebPCoder.git",
"state" : {
"revision" : "95a6838df13bc08d8064cf7e048b787b6e52348d",
"version" : "0.8.4"
}
},
{
"identity" : "siesta",
"kind" : "remoteSourceControl",
"location" : "https://github.com/bustoutsolutions/siesta",
"state" : {
"revision" : "43f34046ebb5beb6802200353c473af303bbc31e",
"version" : "1.5.2"
}
},
{
"identity" : "sparkle",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sparkle-project/Sparkle",
"state" : {
"branch" : "2.x",
"revision" : "f250bead4b943ef9711c61274a1f52e380afa0e8"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
"version" : "1.4.2"
}
},
{
"identity" : "swiftui-introspect",
"kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect.git",
"state" : {
"revision" : "f2616860a41f9d9932da412a8978fec79c06fe24",
"version" : "0.1.4"
}
},
{
"identity" : "swiftyjson",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SwiftyJSON/SwiftyJSON.git",
"state" : {
"revision" : "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07",
"version" : "5.0.1"
}
}
],
"version" : 2
} }