Refine add window and font sizing

This commit is contained in:
nimbold
2026-06-04 04:36:06 +03:30
parent b440b567f1
commit 86d70a9ca7
3 changed files with 84 additions and 51 deletions
+45 -45
View File
@@ -29,20 +29,20 @@ struct AddDownloadsView: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
ScrollView { ScrollView {
VStack(alignment: .leading, spacing: 18) { VStack(alignment: .leading, spacing: 12) {
linkSection linkSection
optionsSection optionsSection
advancedTransferSection advancedTransferSection
summarySection summarySection
previewSection previewSection
} }
.padding(16) .padding(12)
} }
Divider() Divider()
actionBar actionBar
} }
.frame(minWidth: 720, idealWidth: 780, minHeight: 560, idealHeight: 620) .frame(minWidth: 640, idealWidth: 680, minHeight: 500, idealHeight: 540)
.onChange(of: linkText) { _, newValue in .onChange(of: linkText) { _, newValue in
scheduleMetadataRefresh(for: newValue) scheduleMetadataRefresh(for: newValue)
} }
@@ -63,14 +63,14 @@ struct AddDownloadsView: View {
private var linkSection: some View { private var linkSection: some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Label("Download Links", systemImage: "link") Label("Download Links", systemImage: "link")
.font(.headline) .font(.subheadline.weight(.semibold))
TextEditor(text: $linkText) TextEditor(text: $linkText)
.font(.system(.body, design: .monospaced)) .font(.system(.callout, design: .monospaced))
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(.quaternary.opacity(0.35)) .background(.quaternary.opacity(0.35))
.clipShape(RoundedRectangle(cornerRadius: 8)) .clipShape(RoundedRectangle(cornerRadius: 8))
.frame(minHeight: 96) .frame(minHeight: 82)
HStack { HStack {
Text("\(pendingDownloads.count) valid link\(pendingDownloads.count == 1 ? "" : "s") detected") Text("\(pendingDownloads.count) valid link\(pendingDownloads.count == 1 ? "" : "s") detected")
@@ -88,19 +88,19 @@ struct AddDownloadsView: View {
} }
private var optionsSection: some View { private var optionsSection: some View {
Grid(alignment: .leading, horizontalSpacing: 14, verticalSpacing: 12) { Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 8) {
GridRow { GridRow {
Label("Save Location", systemImage: "folder") Label("Save Location", systemImage: "folder")
.font(.headline) .font(.subheadline.weight(.semibold))
Toggle("Use one folder for all files", isOn: $overrideDestination) Toggle("Use one folder for all files", isOn: $overrideDestination)
} }
GridRow { GridRow {
Text("") Text("")
HStack(spacing: 10) { HStack(spacing: 8) {
TextField("Automatic by file type", text: $destinationPath) TextField("Automatic by file type", text: $destinationPath)
.textFieldStyle(.roundedBorder) .textFieldStyle(.roundedBorder)
.font(.system(.body, design: .monospaced)) .font(.system(.callout, design: .monospaced))
.disabled(!overrideDestination) .disabled(!overrideDestination)
Button { Button {
@@ -114,23 +114,23 @@ struct AddDownloadsView: View {
GridRow(alignment: .firstTextBaseline) { GridRow(alignment: .firstTextBaseline) {
Label("Connections per File", systemImage: "point.3.connected.trianglepath.dotted") Label("Connections per File", systemImage: "point.3.connected.trianglepath.dotted")
.font(.headline) .font(.subheadline.weight(.semibold))
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
HStack { HStack {
Slider(value: $connectionsPerServer, in: 1...16, step: 1) Slider(value: $connectionsPerServer, in: 1...16, step: 1)
.frame(width: 170) .frame(width: 145)
Text("\(Int(connectionsPerServer)) segments") Text("\(Int(connectionsPerServer)) segments")
.monospacedDigit() .monospacedDigit()
.frame(width: 110, alignment: .leading) .frame(width: 98, alignment: .leading)
} }
} }
} }
GridRow(alignment: .firstTextBaseline) { GridRow(alignment: .firstTextBaseline) {
Label("Speed Limit", systemImage: "speedometer") Label("Speed Limit", systemImage: "speedometer")
.font(.headline) .font(.subheadline.weight(.semibold))
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
HStack(spacing: 10) { HStack(spacing: 8) {
Toggle("Limit this batch", isOn: $speedLimitEnabled) Toggle("Limit this batch", isOn: $speedLimitEnabled)
.toggleStyle(.switch) .toggleStyle(.switch)
Stepper( Stepper(
@@ -146,20 +146,20 @@ struct AddDownloadsView: View {
GridRow(alignment: .top) { GridRow(alignment: .top) {
Label("Authorization", systemImage: "lock.shield") Label("Authorization", systemImage: "lock.shield")
.font(.headline) .font(.subheadline.weight(.semibold))
.padding(.top, 4) .padding(.top, 4)
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 8) {
Toggle("Use authorization", isOn: $useAuthorization) Toggle("Use authorization", isOn: $useAuthorization)
.toggleStyle(.switch) .toggleStyle(.switch)
if useAuthorization { if useAuthorization {
HStack(spacing: 10) { HStack(spacing: 8) {
TextField("Username", text: $authUsername) TextField("Username", text: $authUsername)
.textFieldStyle(.roundedBorder) .textFieldStyle(.roundedBorder)
.frame(width: 160) .frame(width: 145)
SecureField("Password", text: $authPassword) SecureField("Password", text: $authPassword)
.textFieldStyle(.roundedBorder) .textFieldStyle(.roundedBorder)
.frame(width: 160) .frame(width: 145)
} }
Toggle("Save login for this website", isOn: $saveLogin) Toggle("Save login for this website", isOn: $saveLogin)
.font(.caption) .font(.caption)
@@ -171,7 +171,7 @@ struct AddDownloadsView: View {
} }
private var summarySection: some View { private var summarySection: some View {
HStack(spacing: 10) { HStack(spacing: 8) {
SummaryTile(title: "Files", value: "\(pendingDownloads.count)", symbolName: "doc.on.doc") SummaryTile(title: "Files", value: "\(pendingDownloads.count)", symbolName: "doc.on.doc")
SummaryTile(title: "Required", value: requiredSpaceText, symbolName: "externaldrive") SummaryTile(title: "Required", value: requiredSpaceText, symbolName: "externaldrive")
SummaryTile(title: "Free", value: freeSpaceText, symbolName: "internaldrive") SummaryTile(title: "Free", value: freeSpaceText, symbolName: "internaldrive")
@@ -182,7 +182,7 @@ struct AddDownloadsView: View {
private var previewSection: some View { private var previewSection: some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Label("Preview", systemImage: "list.bullet.rectangle") Label("Preview", systemImage: "list.bullet.rectangle")
.font(.headline) .font(.subheadline.weight(.semibold))
Table(pendingDownloads) { Table(pendingDownloads) {
TableColumn("File") { item in TableColumn("File") { item in
@@ -200,7 +200,7 @@ struct AddDownloadsView: View {
Text(ByteFormatter.string(item.sizeBytes)) Text(ByteFormatter.string(item.sizeBytes))
.monospacedDigit() .monospacedDigit()
} }
.width(95) .width(86)
TableColumn("Save To") { item in TableColumn("Save To") { item in
Text(destinationDirectory(for: item).path) Text(destinationDirectory(for: item).path)
@@ -212,9 +212,9 @@ struct AddDownloadsView: View {
TableColumn("Status") { item in TableColumn("Status") { item in
MetadataStatusView(state: item.state) MetadataStatusView(state: item.state)
} }
.width(130) .width(110)
} }
.frame(minHeight: 170) .frame(minHeight: 135)
} }
} }
@@ -243,17 +243,17 @@ struct AddDownloadsView: View {
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
.disabled(!canAddDownloads) .disabled(!canAddDownloads)
} }
.padding(14) .padding(10)
.background(.bar) .background(.bar)
} }
private var advancedTransferSection: some View { private var advancedTransferSection: some View {
DisclosureGroup(isExpanded: $showsAdvancedTransfer) { DisclosureGroup(isExpanded: $showsAdvancedTransfer) {
Grid(alignment: .leading, horizontalSpacing: 14, verticalSpacing: 12) { Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 8) {
GridRow(alignment: .firstTextBaseline) { GridRow(alignment: .firstTextBaseline) {
Toggle("Checksum", isOn: $checksumEnabled) Toggle("Checksum", isOn: $checksumEnabled)
.font(.headline) .font(.subheadline.weight(.semibold))
HStack(spacing: 10) { HStack(spacing: 8) {
Picker("Algorithm", selection: $checksumAlgorithm) { Picker("Algorithm", selection: $checksumAlgorithm) {
ForEach(ChecksumAlgorithm.allCases) { algorithm in ForEach(ChecksumAlgorithm.allCases) { algorithm in
Text(algorithm.title).tag(algorithm) Text(algorithm.title).tag(algorithm)
@@ -264,45 +264,45 @@ struct AddDownloadsView: View {
TextField("Expected digest", text: $checksumValue) TextField("Expected digest", text: $checksumValue)
.textFieldStyle(.roundedBorder) .textFieldStyle(.roundedBorder)
.font(.system(.body, design: .monospaced)) .font(.system(.callout, design: .monospaced))
} }
.disabled(!checksumEnabled) .disabled(!checksumEnabled)
} }
GridRow(alignment: .top) { GridRow(alignment: .top) {
Label("Headers", systemImage: "text.quote") Label("Headers", systemImage: "text.quote")
.font(.headline) .font(.subheadline.weight(.semibold))
TextEditor(text: $headerText) TextEditor(text: $headerText)
.font(.system(.body, design: .monospaced)) .font(.system(.callout, design: .monospaced))
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(.quaternary.opacity(0.35)) .background(.quaternary.opacity(0.35))
.clipShape(RoundedRectangle(cornerRadius: 8)) .clipShape(RoundedRectangle(cornerRadius: 8))
.frame(minHeight: 60) .frame(minHeight: 48)
} }
GridRow(alignment: .firstTextBaseline) { GridRow(alignment: .firstTextBaseline) {
Label("Cookies", systemImage: "circle.hexagongrid.circle") Label("Cookies", systemImage: "circle.hexagongrid.circle")
.font(.headline) .font(.subheadline.weight(.semibold))
TextField("name=value; other=value", text: $cookieText) TextField("name=value; other=value", text: $cookieText)
.textFieldStyle(.roundedBorder) .textFieldStyle(.roundedBorder)
.font(.system(.body, design: .monospaced)) .font(.system(.callout, design: .monospaced))
} }
GridRow(alignment: .top) { GridRow(alignment: .top) {
Label("Mirrors", systemImage: "point.3.filled.connected.trianglepath.dotted") Label("Mirrors", systemImage: "point.3.filled.connected.trianglepath.dotted")
.font(.headline) .font(.subheadline.weight(.semibold))
TextEditor(text: $mirrorText) TextEditor(text: $mirrorText)
.font(.system(.body, design: .monospaced)) .font(.system(.callout, design: .monospaced))
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(.quaternary.opacity(0.35)) .background(.quaternary.opacity(0.35))
.clipShape(RoundedRectangle(cornerRadius: 8)) .clipShape(RoundedRectangle(cornerRadius: 8))
.frame(minHeight: 60) .frame(minHeight: 48)
} }
} }
.padding(.top, 10) .padding(.top, 8)
} label: { } label: {
Label("Advanced Transfer", systemImage: "slider.horizontal.3") Label("Advanced Transfer", systemImage: "slider.horizontal.3")
.font(.headline) .font(.subheadline.weight(.semibold))
} }
} }
@@ -529,22 +529,22 @@ private struct SummaryTile: View {
var body: some View { var body: some View {
HStack(spacing: 10) { HStack(spacing: 10) {
Image(systemName: symbolName) Image(systemName: symbolName)
.font(.title3) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.frame(width: 24) .frame(width: 20)
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(title) Text(title)
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
Text(value) Text(value)
.font(.headline.monospacedDigit()) .font(.subheadline.weight(.semibold).monospacedDigit())
.lineLimit(1) .lineLimit(1)
} }
Spacer(minLength: 0) Spacer(minLength: 0)
} }
.padding(12) .padding(9)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(minHeight: 64) .frame(minHeight: 52)
.background(.quaternary.opacity(0.35)) .background(.quaternary.opacity(0.35))
.clipShape(RoundedRectangle(cornerRadius: 8)) .clipShape(RoundedRectangle(cornerRadius: 8))
} }
+4 -4
View File
@@ -28,7 +28,7 @@ struct FirelinkApp: App {
.environmentObject(settings) .environmentObject(settings)
.environmentObject(schedulerController) .environmentObject(schedulerController)
.modifier(AppThemeModifier(theme: settings.appTheme)) .modifier(AppThemeModifier(theme: settings.appTheme))
.dynamicTypeSize(settings.appFontSize.dynamicTypeSize) .modifier(AppFontSizeModifier(fontSize: settings.appFontSize))
.frame(minWidth: 1180, idealWidth: 1280, minHeight: 720, idealHeight: 760) .frame(minWidth: 1180, idealWidth: 1280, minHeight: 720, idealHeight: 760)
} }
.windowStyle(.titleBar) .windowStyle(.titleBar)
@@ -38,7 +38,7 @@ struct FirelinkApp: App {
.environmentObject(controller) .environmentObject(controller)
.environmentObject(settings) .environmentObject(settings)
.modifier(AppThemeModifier(theme: settings.appTheme)) .modifier(AppThemeModifier(theme: settings.appTheme))
.dynamicTypeSize(settings.appFontSize.dynamicTypeSize) .modifier(AppFontSizeModifier(fontSize: settings.appFontSize))
} }
.windowResizability(.contentSize) .windowResizability(.contentSize)
@@ -48,11 +48,11 @@ struct FirelinkApp: App {
.environmentObject(controller) .environmentObject(controller)
.environmentObject(settings) .environmentObject(settings)
.modifier(AppThemeModifier(theme: settings.appTheme)) .modifier(AppThemeModifier(theme: settings.appTheme))
.dynamicTypeSize(settings.appFontSize.dynamicTypeSize) .modifier(AppFontSizeModifier(fontSize: settings.appFontSize))
} else { } else {
ContentUnavailableView("Download Not Found", systemImage: "questionmark.circle") ContentUnavailableView("Download Not Found", systemImage: "questionmark.circle")
.modifier(AppThemeModifier(theme: settings.appTheme)) .modifier(AppThemeModifier(theme: settings.appTheme))
.dynamicTypeSize(settings.appFontSize.dynamicTypeSize) .modifier(AppFontSizeModifier(fontSize: settings.appFontSize))
} }
} }
.windowResizability(.contentSize) .windowResizability(.contentSize)
+35 -2
View File
@@ -10,9 +10,31 @@ enum AppFontSize: String, Codable, CaseIterable, Identifiable, Sendable {
var dynamicTypeSize: DynamicTypeSize { var dynamicTypeSize: DynamicTypeSize {
switch self { switch self {
case .small: return .small case .small: return .xSmall
case .standard: return .medium case .standard: return .medium
case .large: return .xLarge case .large: return .xxLarge
}
}
var defaultFont: Font {
switch self {
case .small:
return .system(size: 12)
case .standard:
return .system(size: 13)
case .large:
return .system(size: 15)
}
}
var controlSize: ControlSize {
switch self {
case .small:
return .small
case .standard:
return .regular
case .large:
return .large
} }
} }
} }
@@ -119,6 +141,17 @@ struct AppThemeModifier: ViewModifier {
} }
} }
struct AppFontSizeModifier: ViewModifier {
let fontSize: AppFontSize
func body(content: Content) -> some View {
content
.font(fontSize.defaultFont)
.controlSize(fontSize.controlSize)
.dynamicTypeSize(fontSize.dynamicTypeSize)
}
}
struct ThemeBackgroundModifier: ViewModifier { struct ThemeBackgroundModifier: ViewModifier {
let color: Color? let color: Color?