mirror of
https://github.com/nimbold/Firelink.git
synced 2026-09-04 15:05:22 +00:00
Refine add window and font sizing
This commit is contained in:
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user