import SwiftUI import VoiceCatCore /// iPhone channels tab: a drill-down browser. The root list shows only top-level channels; /// tapping (or VoiceOver-activating) a channel pushes `ChannelDetailView`, which shows the /// people in it and any sub-channels. Joining is an explicit action inside the detail view. struct ChannelBrowserView: View { @Bindable var session: SessionState @State private var showCreateChannel = false var body: some View { NavigationStack { List { ForEach(rootChannels) { ch in NavigationLink { ChannelDetailView(channel: ch, session: session) } label: { ChannelRow(channel: ch, session: session) } .swipeActions(edge: .trailing) { if session.permissions.isAdmin { Button(role: .destructive) { session.deleteChannel(ch.id) } label: { Label("Delete", systemImage: "trash") } } } } } .navigationTitle("Channels") .toolbar { if session.permissions.canCreateTempChannel || session.permissions.isAdmin { ToolbarItem(placement: .primaryAction) { Button { showCreateChannel = true } label: { Image(systemName: "plus") } .accessibilityLabel("Create channel") } } } .sheet(isPresented: $showCreateChannel) { ChannelEditView(channelId: nil, session: session) } } } private var rootChannels: [Channel] { session.channels .filter { $0.parentId == 0 } .sorted { $0.name < $1.name } } } /// Shared channel row used by the iPhone browser and detail views. Mirrors the look of the /// iPad `ChannelTreeView` row but keyed on a `Channel` rather than a tree `ChannelNode`. struct ChannelRow: View { let channel: Channel let session: SessionState var body: some View { let isCurrent = session.currentChannelId == channel.id let usersHere = session.users.filter { $0.channelId == channel.id } HStack(spacing: 8) { Image(systemName: channel.passwordProtected ? "lock.fill" : "number") .foregroundStyle(isCurrent ? .blue : .secondary) .imageScale(.small) VStack(alignment: .leading, spacing: 1) { Text(channel.name) .fontWeight(isCurrent ? .semibold : .regular) if !channel.topic.isEmpty { Text(channel.topic) .font(.caption2) .foregroundStyle(.secondary) .lineLimit(1) } } Spacer() if !usersHere.isEmpty { Text("\(usersHere.count)") .font(.caption) .foregroundStyle(.secondary) } } .accessibilityElement(children: .combine) .accessibilityLabel("\(channel.name)\(isCurrent ? ", current" : "")\(channel.passwordProtected ? ", password protected" : "")\(!usersHere.isEmpty ? ", \(usersHere.count) users" : "")") } }