diff --git a/Shared/Home/FavoriteItemView.swift b/Shared/Home/FavoriteItemView.swift index 3c12c964..3ce20911 100644 --- a/Shared/Home/FavoriteItemView.swift +++ b/Shared/Home/FavoriteItemView.swift @@ -40,6 +40,7 @@ struct FavoriteItemView: View { #endif HorizontalCells(items: store.contentItems) + .environment(\.inChannelView, inChannelView) } .contentShape(Rectangle()) .onAppear { @@ -62,6 +63,15 @@ struct FavoriteItemView: View { } } + var inChannelView: Bool { + switch item.section { + case .channel: + return true + default: + return false + } + } + var itemControl: some View { VStack { #if os(tvOS) diff --git a/Shared/Subscriptions/ChannelsView.swift b/Shared/Subscriptions/ChannelsView.swift index 5a76ea34..658395dd 100644 --- a/Shared/Subscriptions/ChannelsView.swift +++ b/Shared/Subscriptions/ChannelsView.swift @@ -21,6 +21,13 @@ struct ChannelsView: View { } } } + .contextMenu { + Button { + subscriptions.unsubscribe(channel.id) + } label: { + Label("Unsubscribe", systemImage: "xmark.circle") + } + } } #if os(tvOS) .padding(.horizontal, 50) diff --git a/Shared/Views/ChannelAvatarView.swift b/Shared/Views/ChannelAvatarView.swift new file mode 100644 index 00000000..b8fac617 --- /dev/null +++ b/Shared/Views/ChannelAvatarView.swift @@ -0,0 +1,56 @@ +import SwiftUI + +struct ChannelAvatarView: View { + var channel: Channel? + var video: Video? + + @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var subscribedChannels = SubscribedChannelsModel.shared + + var body: some View { + ZStack(alignment: .bottomTrailing) { + Group { + Group { + if let url = channel?.thumbnailURL { + ThumbnailView(url: url) + } else { + ZStack { + Color(white: 0.6) + .opacity(0.5) + + Group { + if let video, video.isLocal { + Image(systemName: video.localStreamImageSystemName) + } else { + Image(systemName: "play.rectangle") + } + } + .foregroundColor(.accentColor) + .font(.system(size: 20)) + .contentShape(Rectangle()) + } + } + } + .clipShape(Circle()) + + if accounts.app.supportsSubscriptions, + accounts.signedIn, + let channel, + subscribedChannels.isSubscribing(channel.id) + { + Image(systemName: "star.circle.fill") + .background(Color.black) + .clipShape(Circle()) + .foregroundColor(.secondary) + } + } + } + .imageScale(.small) + } +} + +struct ChannelAvatarView_Previews: PreviewProvider { + static var previews: some View { + ChannelAvatarView(channel: Video.fixture.channel) + } +} diff --git a/Shared/Views/ChannelPlaylistView.swift b/Shared/Views/ChannelPlaylistView.swift index e58178e1..17e323e8 100644 --- a/Shared/Views/ChannelPlaylistView.swift +++ b/Shared/Views/ChannelPlaylistView.swift @@ -47,7 +47,7 @@ struct ChannelPlaylistView: View { Spacer() - FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title))) + FavoriteButton(item: FavoriteItem(section: .channelPlaylist(accounts.app.appType.rawValue, playlist.id, playlist.title))) .labelStyle(.iconOnly) } diff --git a/Shared/Views/ChannelVideosView.swift b/Shared/Views/ChannelVideosView.swift index 825365ef..2f7fe3e3 100644 --- a/Shared/Views/ChannelVideosView.swift +++ b/Shared/Views/ChannelVideosView.swift @@ -58,7 +58,7 @@ struct ChannelVideosView: View { subscriptionToggleButton if let channel = presentedChannel { - FavoriteButton(item: FavoriteItem(section: .channel(channel.id, channel.name))) + FavoriteButton(item: FavoriteItem(section: .channel(accounts.app.appType.rawValue, channel.id, channel.name))) .labelStyle(.iconOnly) } } @@ -124,7 +124,7 @@ struct ChannelVideosView: View { ToolbarItem { if let presentedChannel { - FavoriteButton(item: FavoriteItem(section: .channel(presentedChannel.id, presentedChannel.name))) + FavoriteButton(item: FavoriteItem(section: .channel(accounts.app.appType.rawValue, presentedChannel.id, presentedChannel.name))) } } #endif @@ -159,28 +159,12 @@ struct ChannelVideosView: View { } var thumbnail: some View { - Group { - if let thumbnail = store.item?.thumbnailURL { - WebImage(url: thumbnail) - .resizable() - } else { - ZStack { - Color(white: 0.6) - .opacity(0.5) - - Image(systemName: "play.rectangle") - .foregroundColor(.accentColor) - .imageScale(.small) - .contentShape(Rectangle()) - } - } - } + ChannelAvatarView(channel: store.item) #if os(tvOS) - .frame(width: 80, height: 80, alignment: .trailing) + .frame(width: 80, height: 80, alignment: .trailing) #else - .frame(width: 30, height: 30, alignment: .trailing) + .frame(width: 30, height: 30, alignment: .trailing) #endif - .clipShape(Circle()) } @ViewBuilder var banner: some View { @@ -250,7 +234,8 @@ struct ChannelVideosView: View { subscriptionsLabel if presentedChannel?.verified ?? false { - Text("Verified") + Image(systemName: "checkmark.seal.fill") + .imageScale(.small) } viewsLabel @@ -308,7 +293,7 @@ struct ChannelVideosView: View { subscriptionToggleButtonDisabled = false } } label: { - Label("Unsubscribe", systemImage: "star.circle") + Label("Unsubscribe", systemImage: "xmark.circle") #if os(iOS) .labelStyle(.automatic) #else diff --git a/Shared/Views/ControlsBar.swift b/Shared/Views/ControlsBar.swift index d03a3e2a..63ad7937 100644 --- a/Shared/Views/ControlsBar.swift +++ b/Shared/Views/ControlsBar.swift @@ -144,22 +144,11 @@ struct ControlsBar: View { ) } } label: { - ZStack(alignment: .bottomTrailing) { - authorAvatar - - if accounts.app.supportsSubscriptions, - accounts.signedIn, - let video = model.currentVideo, - subscriptions.isSubscribing(video.channel.id) - { - Image(systemName: "star.circle.fill") - #if !os(tvOS) - .background(Color.background) - #endif - .clipShape(Circle()) - .foregroundColor(.secondary) - } - } + ChannelAvatarView( + channel: model.currentVideo?.channel, + video: model.currentVideo + ) + .frame(width: barHeight - 10, height: barHeight - 10) } .contextMenu { if let video = model.currentVideo { @@ -207,7 +196,7 @@ struct ControlsBar: View { navigation.presentUnsubscribeAlert(video.channel, subscriptions: subscriptions) #endif } label: { - Label("Unsubscribe", systemImage: "xmark.circle") + Label("Unsubscribe", systemImage: "star.circle") } } else { Button { @@ -275,38 +264,6 @@ struct ControlsBar: View { Spacer() } } - - private var authorAvatar: some View { - Group { - if let url = model.currentItem?.video?.channel.thumbnailURL { - WebImage(url: url, options: [.lowPriority]) - .resizable() - .placeholder { - Rectangle().fill(Color("PlaceholderColor")) - } - .retryOnAppear(true) - .indicator(.activity) - } else { - ZStack { - Color(white: 0.6) - .opacity(0.5) - - Group { - if let video = model.currentItem?.video, video.isLocal { - Image(systemName: video.localStreamImageSystemName) - } else { - Image(systemName: "play.rectangle") - } - } - .foregroundColor(.accentColor) - .font(.system(size: 20)) - .contentShape(Rectangle()) - } - } - } - .frame(width: 44, height: 44, alignment: .leading) - .clipShape(Circle()) - } } struct ControlsBar_Previews: PreviewProvider { diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index 8bcf8b91..a6033470 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -531,6 +531,9 @@ 3774127827387EB000423605 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 3774127727387EB000423605 /* Logging */; }; 3774127A27387EBC00423605 /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 3774127927387EBC00423605 /* Defaults */; }; 3774127C27387EC800423605 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 3774127B27387EC800423605 /* Alamofire */; }; + 3776924E294630110055EC18 /* ChannelAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776924D294630110055EC18 /* ChannelAvatarView.swift */; }; + 3776924F294630110055EC18 /* ChannelAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776924D294630110055EC18 /* ChannelAvatarView.swift */; }; + 37769250294630110055EC18 /* ChannelAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776924D294630110055EC18 /* ChannelAvatarView.swift */; }; 3776ADD6287381240078EBC4 /* Captions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776ADD5287381240078EBC4 /* Captions.swift */; }; 3776ADD7287381240078EBC4 /* Captions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776ADD5287381240078EBC4 /* Captions.swift */; }; 3776ADD8287381240078EBC4 /* Captions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776ADD5287381240078EBC4 /* Captions.swift */; }; @@ -1248,6 +1251,7 @@ 37732FEF2703A26300F04329 /* AccountValidationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountValidationStatus.swift; sourceTree = ""; }; 37732FF32703D32400F04329 /* Sidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sidebar.swift; sourceTree = ""; }; 37737785276F9858000521C1 /* Windows.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Windows.swift; sourceTree = ""; }; + 3776924D294630110055EC18 /* ChannelAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAvatarView.swift; sourceTree = ""; }; 3776ADD5287381240078EBC4 /* Captions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Captions.swift; path = Model/Captions.swift; sourceTree = SOURCE_ROOT; }; 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedContentAccessors.swift; sourceTree = ""; }; 377ABC3F286E4AD5009C986F /* InstancesManifest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstancesManifest.swift; sourceTree = ""; }; @@ -1759,6 +1763,7 @@ isa = PBXGroup; children = ( 37E6D79F2944CD3800550C3D /* CacheStatusHeader.swift */, + 3776924D294630110055EC18 /* ChannelAvatarView.swift */, 3743B86727216D3600261544 /* ChannelCell.swift */, 37C3A24827235FAA0087A57A /* ChannelPlaylistCell.swift */, 37C3A250272366440087A57A /* ChannelPlaylistView.swift */, @@ -3166,6 +3171,7 @@ 379B0253287A1CDF001015B5 /* OrientationTracker.swift in Sources */, 37E80F3C287B107F00561799 /* VideoDetailsOverlay.swift in Sources */, 370B79C9286279810045DB77 /* NSObject+Swizzle.swift in Sources */, + 3776924E294630110055EC18 /* ChannelAvatarView.swift in Sources */, 37D4B0E42671614900C925CA /* YatteeApp.swift in Sources */, 37C3A241272359900087A57A /* Double+Format.swift in Sources */, 3784CDE227772EE40055BBF2 /* Watch.swift in Sources */, @@ -3280,6 +3286,7 @@ 377FC7E2267A084A00A6BBAF /* VideoCell.swift in Sources */, 37CC3F51270D010D00608308 /* VideoBanner.swift in Sources */, 37F961A027BD90BB00058149 /* PlayerBackendType.swift in Sources */, + 3776924F294630110055EC18 /* ChannelAvatarView.swift in Sources */, 37BC50AD2778BCBA00510953 /* HistoryModel.swift in Sources */, 3752069E285E910600CA655F /* ChapterView.swift in Sources */, 37030FF827B0347C00ECDDAA /* MPVPlayerView.swift in Sources */, @@ -3568,6 +3575,7 @@ 37B044B926F7AB9000E1419D /* SettingsView.swift in Sources */, 3743B86A27216D3600261544 /* ChannelCell.swift in Sources */, 3751BA8527E6914F007B1A60 /* ReturnYouTubeDislikeAPI.swift in Sources */, + 37769250294630110055EC18 /* ChannelAvatarView.swift in Sources */, 37030FFD27B0398000ECDDAA /* MPVClient.swift in Sources */, 378E9C4229455A5800B2D696 /* ChannelsView.swift in Sources */, 37192D5928B179D60012EEDD /* ChaptersView.swift in Sources */,