Files
yattee/Shared/Subscriptions/ChannelsView.swift
Arkadiusz Fal 757b4cb671 Add compact list row styling for iOS channels view
Apply compact row styling to channel list items on iOS to reduce vertical spacing and improve visual density. Uses listRowSpacing(0) on iOS 15+ and custom insets for consistent padding across all iOS versions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 18:14:34 +01:00

212 lines
7.0 KiB
Swift

import Defaults
import SDWebImageSwiftUI
import SwiftUI
struct ChannelsView: View {
@ObservedObject private var feed = FeedModel.shared
@ObservedObject private var subscriptions = SubscribedChannelsModel.shared
@ObservedObject private var accounts = AccountsModel.shared
@ObservedObject private var feedCount = UnwatchedFeedCountModel.shared
private var navigation = NavigationModel.shared
@Default(.showCacheStatus) private var showCacheStatus
@Default(.showUnwatchedFeedBadges) private var showUnwatchedFeedBadges
@Default(.keepChannelsWithUnwatchedFeedOnTop) private var keepChannelsWithUnwatchedFeedOnTop
@Default(.showChannelAvatarInChannelsLists) private var showChannelAvatarInChannelsLists
@State private var channelLinkActive = false
@State private var channelForLink: Channel?
var body: some View {
List {
Section(header: header) {
ForEach(channels) { channel in
let label = HStack {
if showChannelAvatarInChannelsLists {
ChannelAvatarView(channel: channel, subscribedBadge: false)
.frame(width: 35, height: 35)
} else {
Image(systemName: RecentsModel.symbolSystemImage(channel.name))
.imageScale(.large)
.foregroundColor(.accentColor)
.frame(width: 35, height: 35)
}
Text(channel.name)
.lineLimit(1)
}
.backport
.badge(showUnwatchedFeedBadges ? feedCount.unwatchedByChannelText(channel) : nil)
Group {
#if os(tvOS)
Button {
navigation.openChannel(channel, navigationStyle: .tab)
} label: {
label
}
#else
Button {
channelForLink = channel
channelLinkActive = channelForLink != nil
} label: {
label
.contentShape(Rectangle())
.foregroundColor(.primary)
}
.buttonStyle(.plain)
#endif
}
.contextMenu {
if subscriptions.isSubscribing(channel.id) {
toggleWatchedButton(channel)
}
Button {
subscriptions.unsubscribe(channel.id)
} label: {
Label("Unsubscribe", systemImage: "xmark.circle")
}
}
#if os(iOS)
.modifier(CompactListRowModifier())
#endif
}
#if os(tvOS)
.padding(.horizontal, 50)
#endif
Color.clear.padding(.bottom, 50)
.listRowBackground(Color.clear)
.backport
.listRowSeparator(false)
}
}
#if !os(tvOS)
.background(
NavigationLink(destination: ChannelVideosView(channel: channelForLink ?? Video.fixture.channel), isActive: $channelLinkActive, label: EmptyView.init)
)
#endif
.onAppear {
subscriptions.load()
}
.onChange(of: accounts.current) { _ in
subscriptions.load(force: true)
}
#if os(iOS)
.refreshControl { refreshControl in
subscriptions.load(force: true) {
refreshControl.endRefreshing()
}
}
.backport
.refreshable {
await subscriptions.load(force: true)
}
#endif
#if !os(tvOS)
.background(
Button("Refresh") {
subscriptions.load(force: true)
}
.keyboardShortcut("r")
.opacity(0)
)
#endif
#if !os(macOS)
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
subscriptions.load()
}
#endif
#if os(tvOS)
.padding(.horizontal, 30)
#endif
}
var channels: [Channel] {
keepChannelsWithUnwatchedFeedOnTop ? subscriptions.allByUnwatchedCount : subscriptions.all
}
var header: some View {
HStack {
#if os(tvOS)
SubscriptionsPageButton()
#endif
if showCacheStatus {
Spacer()
CacheStatusHeader(
refreshTime: subscriptions.formattedCacheTime,
isLoading: subscriptions.isLoading
)
}
#if os(tvOS)
if !showCacheStatus {
Spacer()
}
Button {
subscriptions.load(force: true)
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
.labelStyle(.iconOnly)
.imageScale(.small)
.font(.caption)
}
#endif
}
#if os(tvOS)
.padding(.bottom, 15)
.padding(.top, 15)
#endif
}
@ViewBuilder func toggleWatchedButton(_ channel: Channel) -> some View {
if feed.canMarkChannelAsWatched(channel.id) {
markChannelAsWatchedButton(channel)
} else {
markChannelAsUnwatchedButton(channel)
}
}
func markChannelAsWatchedButton(_ channel: Channel) -> some View {
Button {
feed.markChannelAsWatched(channel.id)
} label: {
Label("Mark channel feed as watched", systemImage: "checkmark.circle.fill")
}
.disabled(!feed.canMarkAllFeedAsWatched)
}
func markChannelAsUnwatchedButton(_ channel: Channel) -> some View {
Button {
feed.markChannelAsUnwatched(channel.id)
} label: {
Label("Mark channel feed as unwatched", systemImage: "checkmark.circle")
}
}
}
#if os(iOS)
struct CompactListRowModifier: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 15.0, *) {
content
.listRowSpacing(0)
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
} else {
content
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
}
}
}
#endif
struct ChannelsView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ChannelsView()
}
}
}