mirror of
https://github.com/yattee/yattee.git
synced 2026-05-13 02:45:03 +00:00
Make sources list feel native on macOS
Drop the iOS-grouped rounded card, per-row chevron, and oversized metrics on macOS. Use tighter padding, smaller icon/title fonts, uppercase section headers, and top/bottom dividers so the list reads like a native grouped Mac list. Force .buttonStyle(.plain) on row buttons/NavigationLinks and add .contentShape(Rectangle()) so the full row is hit-testable without picking up macOS's default link styling. iOS and tvOS unchanged.
This commit is contained in:
@@ -17,13 +17,25 @@ struct SourceListRow<Content: View>: View {
|
||||
@ViewBuilder let content: () -> Content
|
||||
|
||||
/// Horizontal padding for row content.
|
||||
#if os(macOS)
|
||||
private let horizontalPadding: CGFloat = 12
|
||||
#else
|
||||
private let horizontalPadding: CGFloat = 16
|
||||
#endif
|
||||
|
||||
/// Vertical padding for row content.
|
||||
#if os(macOS)
|
||||
private let verticalPadding: CGFloat = 8
|
||||
#else
|
||||
private let verticalPadding: CGFloat = 12
|
||||
#endif
|
||||
|
||||
/// Width of the icon column.
|
||||
#if os(macOS)
|
||||
private let iconWidth: CGFloat = 24
|
||||
#else
|
||||
private let iconWidth: CGFloat = 32
|
||||
#endif
|
||||
|
||||
/// Spacing between icon and text.
|
||||
private let iconTextSpacing: CGFloat = 12
|
||||
|
||||
@@ -170,6 +170,16 @@ struct MediaSourcesView: View {
|
||||
|
||||
@ViewBuilder
|
||||
private func sectionHeader(_ title: String) -> some View {
|
||||
#if os(macOS)
|
||||
Text(title)
|
||||
.font(.subheadline)
|
||||
.textCase(.uppercase)
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 12)
|
||||
.padding(.bottom, 4)
|
||||
#else
|
||||
Text(title)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundStyle(.secondary)
|
||||
@@ -177,6 +187,7 @@ struct MediaSourcesView: View {
|
||||
.padding(.horizontal, listStyle == .inset ? 32 : 16)
|
||||
.padding(.top, 16)
|
||||
.padding(.bottom, 8)
|
||||
#endif
|
||||
}
|
||||
|
||||
private var sourcesList: some View {
|
||||
@@ -333,6 +344,16 @@ struct MediaSourcesView: View {
|
||||
|
||||
@ViewBuilder
|
||||
private func sectionCard<Content: View>(@ViewBuilder content: () -> Content) -> some View {
|
||||
#if os(macOS)
|
||||
VStack(spacing: 0) {
|
||||
Divider()
|
||||
LazyVStack(spacing: 0) {
|
||||
content()
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
.padding(.bottom, 12)
|
||||
#else
|
||||
if listStyle == .inset {
|
||||
LazyVStack(spacing: 0) {
|
||||
content()
|
||||
@@ -347,6 +368,7 @@ struct MediaSourcesView: View {
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -364,7 +386,11 @@ struct MediaSourcesView: View {
|
||||
NavigationLink(value: NavigationDestination.instanceBrowse(instance)) {
|
||||
instanceRow(instance)
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.plain)
|
||||
#else
|
||||
.foregroundStyle(.primary)
|
||||
#endif
|
||||
}
|
||||
#if os(tvOS)
|
||||
.modifier(FirstRowFocusModifier(isFirst: isFirst, focus: $firstSourceFocused))
|
||||
@@ -416,12 +442,20 @@ struct MediaSourcesView: View {
|
||||
} label: {
|
||||
mediaSourceRow(source, needsPassword: true)
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.plain)
|
||||
#else
|
||||
.foregroundStyle(.primary)
|
||||
#endif
|
||||
} else {
|
||||
NavigationLink(value: NavigationDestination.mediaBrowser(source, path: "/")) {
|
||||
mediaSourceRow(source, needsPassword: false)
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.plain)
|
||||
#else
|
||||
.foregroundStyle(.primary)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if os(tvOS)
|
||||
@@ -460,15 +494,24 @@ struct MediaSourcesView: View {
|
||||
#else
|
||||
let rowSpacing: CGFloat = 12
|
||||
#endif
|
||||
#if os(macOS)
|
||||
let iconFont: Font = .title3
|
||||
let iconFrameWidth: CGFloat = 24
|
||||
let titleFont: Font = .body
|
||||
#else
|
||||
let iconFont: Font = .title2
|
||||
let iconFrameWidth: CGFloat = 32
|
||||
let titleFont: Font = .headline
|
||||
#endif
|
||||
return HStack(spacing: rowSpacing) {
|
||||
Image(systemName: instance.type.systemImage)
|
||||
.font(.title2)
|
||||
.font(iconFont)
|
||||
.foregroundStyle(.tint)
|
||||
.frame(width: 32)
|
||||
.frame(width: iconFrameWidth)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(instance.displayName)
|
||||
.font(.headline)
|
||||
.font(titleFont)
|
||||
.foregroundStyle(.primary)
|
||||
|
||||
Text("\(instance.type.displayName) - \(instance.url.host ?? instance.url.absoluteString)")
|
||||
@@ -478,10 +521,13 @@ struct MediaSourcesView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
#if !os(macOS)
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
#endif
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
|
||||
private func mediaSourceRow(_ source: MediaSource, needsPassword: Bool) -> some View {
|
||||
@@ -490,15 +536,24 @@ struct MediaSourcesView: View {
|
||||
#else
|
||||
let rowSpacing: CGFloat = 12
|
||||
#endif
|
||||
#if os(macOS)
|
||||
let iconFont: Font = .title3
|
||||
let iconFrameWidth: CGFloat = 24
|
||||
let titleFont: Font = .body
|
||||
#else
|
||||
let iconFont: Font = .title2
|
||||
let iconFrameWidth: CGFloat = 32
|
||||
let titleFont: Font = .headline
|
||||
#endif
|
||||
return HStack(spacing: rowSpacing) {
|
||||
Image(systemName: source.type.systemImage)
|
||||
.font(.title2)
|
||||
.font(iconFont)
|
||||
.foregroundStyle(.tint)
|
||||
.frame(width: 32)
|
||||
.frame(width: iconFrameWidth)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(source.name)
|
||||
.font(.headline)
|
||||
.font(titleFont)
|
||||
.foregroundStyle(.primary)
|
||||
|
||||
Text("\(source.type.displayName) - \(source.urlDisplayString)")
|
||||
@@ -514,10 +569,13 @@ struct MediaSourcesView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
#if !os(macOS)
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
#endif
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -174,6 +174,16 @@ struct SourcesListView: View {
|
||||
|
||||
@ViewBuilder
|
||||
private func sectionHeader(_ title: String) -> some View {
|
||||
#if os(macOS)
|
||||
Text(title)
|
||||
.font(.subheadline)
|
||||
.textCase(.uppercase)
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 12)
|
||||
.padding(.bottom, 4)
|
||||
#else
|
||||
Text(title)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundStyle(.secondary)
|
||||
@@ -181,12 +191,23 @@ struct SourcesListView: View {
|
||||
.padding(.horizontal, listStyle == .inset ? 32 : 16)
|
||||
.padding(.top, 16)
|
||||
.padding(.bottom, 8)
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Section Card
|
||||
|
||||
@ViewBuilder
|
||||
private func sectionCard<Content: View>(@ViewBuilder content: () -> Content) -> some View {
|
||||
#if os(macOS)
|
||||
VStack(spacing: 0) {
|
||||
Divider()
|
||||
LazyVStack(spacing: 0) {
|
||||
content()
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
.padding(.bottom, 12)
|
||||
#else
|
||||
if listStyle == .inset {
|
||||
LazyVStack(spacing: 0) {
|
||||
content()
|
||||
@@ -205,6 +226,7 @@ struct SourcesListView: View {
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Remote Servers Section
|
||||
@@ -244,7 +266,11 @@ struct SourcesListView: View {
|
||||
} label: {
|
||||
instanceRow(instance)
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.plain)
|
||||
#else
|
||||
.foregroundStyle(.primary)
|
||||
#endif
|
||||
}
|
||||
.swipeActions {
|
||||
SwipeAction(symbolImage: "pencil", tint: .white, background: .orange) { reset in
|
||||
@@ -280,16 +306,25 @@ struct SourcesListView: View {
|
||||
#else
|
||||
let rowSpacing: CGFloat = 12
|
||||
#endif
|
||||
#if os(macOS)
|
||||
let iconFont: Font = .title3
|
||||
let iconFrameWidth: CGFloat = 24
|
||||
let titleFont: Font = .body
|
||||
#else
|
||||
let iconFont: Font = .title2
|
||||
let iconFrameWidth: CGFloat = 32
|
||||
let titleFont: Font = .headline
|
||||
#endif
|
||||
return HStack(spacing: rowSpacing) {
|
||||
Image(systemName: instance.type.systemImage)
|
||||
.font(.title2)
|
||||
.font(iconFont)
|
||||
.foregroundStyle(.tint)
|
||||
.frame(width: 32)
|
||||
.frame(width: iconFrameWidth)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack {
|
||||
Text(instance.displayName)
|
||||
.font(.headline)
|
||||
.font(titleFont)
|
||||
.foregroundStyle(.primary)
|
||||
|
||||
if !instance.isEnabled {
|
||||
@@ -306,10 +341,13 @@ struct SourcesListView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
#if !os(macOS)
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.tertiary)
|
||||
#endif
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -380,7 +418,11 @@ struct SourcesListView: View {
|
||||
} label: {
|
||||
mediaSourceRow(source, needsPassword: needsPassword)
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.plain)
|
||||
#else
|
||||
.foregroundStyle(.primary)
|
||||
#endif
|
||||
}
|
||||
.swipeActions {
|
||||
SwipeAction(symbolImage: "pencil", tint: .white, background: .orange) { reset in
|
||||
@@ -416,16 +458,25 @@ struct SourcesListView: View {
|
||||
#else
|
||||
let rowSpacing: CGFloat = 12
|
||||
#endif
|
||||
#if os(macOS)
|
||||
let iconFont: Font = .title3
|
||||
let iconFrameWidth: CGFloat = 24
|
||||
let titleFont: Font = .body
|
||||
#else
|
||||
let iconFont: Font = .title2
|
||||
let iconFrameWidth: CGFloat = 32
|
||||
let titleFont: Font = .headline
|
||||
#endif
|
||||
return HStack(spacing: rowSpacing) {
|
||||
Image(systemName: source.type.systemImage)
|
||||
.font(.title2)
|
||||
.font(iconFont)
|
||||
.foregroundStyle(.tint)
|
||||
.frame(width: 32)
|
||||
.frame(width: iconFrameWidth)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack {
|
||||
Text(source.name)
|
||||
.font(.headline)
|
||||
.font(titleFont)
|
||||
.foregroundStyle(.primary)
|
||||
|
||||
if !source.isEnabled {
|
||||
@@ -446,10 +497,13 @@ struct SourcesListView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
#if !os(macOS)
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.tertiary)
|
||||
#endif
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
|
||||
// MARK: - Disabled Badge
|
||||
|
||||
Reference in New Issue
Block a user