mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 01:39:46 +00:00
Yattee v2 rewrite
This commit is contained in:
217
Yattee/Views/Settings/NetworkShareDiscoverySheet.swift
Normal file
217
Yattee/Views/Settings/NetworkShareDiscoverySheet.swift
Normal file
@@ -0,0 +1,217 @@
|
||||
//
|
||||
// NetworkShareDiscoverySheet.swift
|
||||
// Yattee
|
||||
//
|
||||
// Sheet for discovering and selecting network shares (WebDAV/SMB) via Bonjour.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct NetworkShareDiscoverySheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.appEnvironment) private var appEnvironment
|
||||
|
||||
/// Called when user selects a share.
|
||||
let onSelect: (DiscoveredShare) -> Void
|
||||
|
||||
/// Optional filter to show only specific share types.
|
||||
let filterType: DiscoveredShare.ShareType?
|
||||
|
||||
init(filterType: DiscoveredShare.ShareType? = nil, onSelect: @escaping (DiscoveredShare) -> Void) {
|
||||
self.filterType = filterType
|
||||
self.onSelect = onSelect
|
||||
}
|
||||
|
||||
private var discoveryService: NetworkShareDiscoveryService? {
|
||||
appEnvironment?.networkShareDiscoveryService
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
content
|
||||
.navigationTitle(String(localized: "discovery.title"))
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(String(localized: "common.cancel")) {
|
||||
discoveryService?.stopDiscovery()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
discoveryService?.startDiscovery()
|
||||
}
|
||||
.onDisappear {
|
||||
discoveryService?.stopDiscovery()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
if let service = discoveryService {
|
||||
List {
|
||||
// Scanning indicator
|
||||
if service.isScanning {
|
||||
Section {
|
||||
HStack {
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
Text(String(localized: "discovery.scanning"))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filtered shares based on filterType
|
||||
let shares = filteredShares(from: service.discoveredShares)
|
||||
|
||||
if shares.isEmpty && !service.isScanning {
|
||||
// Empty state
|
||||
Section {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "discovery.empty.title"), systemImage: "network.slash")
|
||||
} description: {
|
||||
Text(String(localized: "discovery.empty.description"))
|
||||
} actions: {
|
||||
Button(String(localized: "discovery.scanAgain")) {
|
||||
service.startDiscovery()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Group shares by type
|
||||
let groupedShares = Dictionary(grouping: shares) { $0.type }
|
||||
|
||||
// WebDAV shares (combined webdav and webdavs)
|
||||
let webdavShares = (groupedShares[.webdav] ?? []) + (groupedShares[.webdavs] ?? [])
|
||||
if !webdavShares.isEmpty {
|
||||
Section {
|
||||
ForEach(webdavShares) { share in
|
||||
ShareRow(share: share) {
|
||||
selectShare(share)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text(String(localized: "discovery.section.webdav"))
|
||||
}
|
||||
}
|
||||
|
||||
// SMB shares
|
||||
if let smbShares = groupedShares[.smb], !smbShares.isEmpty {
|
||||
Section {
|
||||
ForEach(smbShares) { share in
|
||||
ShareRow(share: share) {
|
||||
selectShare(share)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text(String(localized: "discovery.section.smb"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#if os(tvOS)
|
||||
.listStyle(.grouped)
|
||||
#endif
|
||||
} else {
|
||||
// No environment available
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "discovery.unavailable.title"), systemImage: "exclamationmark.triangle")
|
||||
} description: {
|
||||
Text(String(localized: "discovery.unavailable.description"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func filteredShares(from shares: [DiscoveredShare]) -> [DiscoveredShare] {
|
||||
guard let filterType else { return shares }
|
||||
|
||||
switch filterType {
|
||||
case .webdav, .webdavs:
|
||||
// When filtering for WebDAV, include both HTTP and HTTPS variants
|
||||
return shares.filter { $0.type == .webdav || $0.type == .webdavs }
|
||||
case .smb:
|
||||
return shares.filter { $0.type == .smb }
|
||||
}
|
||||
}
|
||||
|
||||
private func selectShare(_ share: DiscoveredShare) {
|
||||
discoveryService?.stopDiscovery()
|
||||
onSelect(share)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Share Row
|
||||
|
||||
private struct ShareRow: View {
|
||||
let share: DiscoveredShare
|
||||
let onTap: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: onTap) {
|
||||
HStack {
|
||||
Image(systemName: share.type.systemImage)
|
||||
.foregroundStyle(iconColor)
|
||||
.frame(width: 24)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(share.name)
|
||||
.font(.body)
|
||||
.foregroundStyle(.primary)
|
||||
|
||||
Text(addressDisplay)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if share.type == .webdavs {
|
||||
Image(systemName: "lock.fill")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.green)
|
||||
}
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
private var iconColor: Color {
|
||||
switch share.type {
|
||||
case .webdav: .blue
|
||||
case .webdavs: .green
|
||||
case .smb: .orange
|
||||
}
|
||||
}
|
||||
|
||||
private var addressDisplay: String {
|
||||
var display = share.host
|
||||
|
||||
if let port = share.port {
|
||||
display += ":\(port)"
|
||||
}
|
||||
|
||||
if let path = share.path, !path.isEmpty {
|
||||
display += path.hasPrefix("/") ? path : "/\(path)"
|
||||
}
|
||||
|
||||
return display
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview {
|
||||
NetworkShareDiscoverySheet { _ in }
|
||||
.appEnvironment(.preview)
|
||||
}
|
||||
Reference in New Issue
Block a user