mirror of
https://github.com/yattee/yattee.git
synced 2026-05-13 10:55:03 +00:00
Make source add/edit forms feel native on macOS
Use grouped Form style with LabeledContent rows and move primary actions into the sheet toolbar for SMB, WebDAV, Local Folder, Remote Server and the Edit sheet. iOS and tvOS branches unchanged.
This commit is contained in:
@@ -34,6 +34,9 @@ struct AddLocalFolderView: View {
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
macOSBody
|
||||
#else
|
||||
Form {
|
||||
nameSection
|
||||
folderSection
|
||||
@@ -53,8 +56,64 @@ struct AddLocalFolderView: View {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
private var macOSBody: some View {
|
||||
Form {
|
||||
Section {
|
||||
LabeledContent(String(localized: "sources.field.name")) {
|
||||
TextField("", text: $name)
|
||||
}
|
||||
} footer: {
|
||||
Text(String(localized: "sources.footer.displayName"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
LabeledContent(String(localized: "sources.header.folder")) {
|
||||
HStack {
|
||||
if let url = selectedFolderURL {
|
||||
Text(url.path)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
} else {
|
||||
Text(String(localized: "sources.selectFolder"))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Button(String(localized: "sources.selectFolder")) {
|
||||
selectFolderMacOS()
|
||||
}
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text(String(localized: "sources.footer.folder"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
if let result = testResult {
|
||||
SourceTestResultSection(result: result)
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle(String(localized: "sources.addLocalFolder"))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(String(localized: "sources.addSource")) {
|
||||
addSource()
|
||||
}
|
||||
.disabled(!canAdd)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: - Sections
|
||||
|
||||
private var nameSection: some View {
|
||||
|
||||
@@ -172,12 +172,38 @@ struct AddRemoteServerView: View {
|
||||
|
||||
if isFieldsRevealed {
|
||||
serverConfigurationFields
|
||||
#if !os(macOS)
|
||||
actionSection
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
#endif
|
||||
#if os(macOS)
|
||||
.formStyle(.grouped)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
if isFieldsRevealed {
|
||||
Button {
|
||||
addSource()
|
||||
} label: {
|
||||
if isValidatingCredentials {
|
||||
HStack(spacing: 6) {
|
||||
ProgressView().controlSize(.small)
|
||||
Text(String(localized: "sources.validatingCredentials"))
|
||||
}
|
||||
} else {
|
||||
Text(String(localized: "sources.addSource"))
|
||||
}
|
||||
}
|
||||
.disabled(!canAdd || isValidatingCredentials)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
.accessibilityIdentifier("addRemoteServer.actionButton")
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - URL Entry Section
|
||||
@@ -207,6 +233,35 @@ struct AddRemoteServerView: View {
|
||||
.buttonStyle(TVSettingsButtonStyle())
|
||||
.disabled(urlString.isEmpty || uiState == .detecting)
|
||||
}
|
||||
#elseif os(macOS)
|
||||
LabeledContent(String(localized: "sources.field.url")) {
|
||||
TextField("", text: $urlString, prompt: Text(String(localized: "sources.placeholder.urlOrAddress")))
|
||||
.textContentType(.URL)
|
||||
.autocorrectionDisabled()
|
||||
.accessibilityIdentifier("addRemoteServer.urlField")
|
||||
.onChange(of: urlString) { _, _ in
|
||||
handleURLChange()
|
||||
}
|
||||
}
|
||||
|
||||
if !isFieldsRevealed && !isAwaitingBasicAuth {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button {
|
||||
startDetection()
|
||||
} label: {
|
||||
HStack(spacing: 6) {
|
||||
if case .detecting = uiState {
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
}
|
||||
Text(String(localized: "sources.detect"))
|
||||
}
|
||||
}
|
||||
.disabled(urlString.isEmpty || uiState == .detecting)
|
||||
.accessibilityIdentifier("addRemoteServer.detectButton")
|
||||
}
|
||||
}
|
||||
#else
|
||||
TextField(String(localized: "sources.placeholder.urlOrAddress"), text: $urlString)
|
||||
.textContentType(.URL)
|
||||
@@ -277,6 +332,16 @@ struct AddRemoteServerView: View {
|
||||
#if os(tvOS)
|
||||
TVSettingsTextField(title: String(localized: "sources.field.username"), text: $basicAuthUsername)
|
||||
TVSettingsTextField(title: String(localized: "sources.field.password"), text: $basicAuthPassword, isSecure: true)
|
||||
#elseif os(macOS)
|
||||
LabeledContent(String(localized: "sources.field.username")) {
|
||||
TextField("", text: $basicAuthUsername)
|
||||
.textContentType(.username)
|
||||
.autocorrectionDisabled()
|
||||
}
|
||||
LabeledContent(String(localized: "sources.field.password")) {
|
||||
SecureField("", text: $basicAuthPassword)
|
||||
.textContentType(.password)
|
||||
}
|
||||
#else
|
||||
TextField(String(localized: "sources.field.username"), text: $basicAuthUsername)
|
||||
.textContentType(.username)
|
||||
@@ -328,6 +393,10 @@ struct AddRemoteServerView: View {
|
||||
Section {
|
||||
#if os(tvOS)
|
||||
TVSettingsTextField(title: String(localized: "sources.field.nameOptional"), text: $name)
|
||||
#elseif os(macOS)
|
||||
LabeledContent(String(localized: "sources.field.name")) {
|
||||
TextField("", text: $name, prompt: Text(String(localized: "sources.field.nameOptional")))
|
||||
}
|
||||
#else
|
||||
TextField(String(localized: "sources.field.nameOptional"), text: $name)
|
||||
#endif
|
||||
@@ -381,6 +450,16 @@ struct AddRemoteServerView: View {
|
||||
#if os(tvOS)
|
||||
TVSettingsTextField(title: String(localized: "sources.field.username"), text: $basicAuthUsername)
|
||||
TVSettingsTextField(title: String(localized: "sources.field.password"), text: $basicAuthPassword, isSecure: true)
|
||||
#elseif os(macOS)
|
||||
LabeledContent(String(localized: "sources.field.username")) {
|
||||
TextField("", text: $basicAuthUsername)
|
||||
.textContentType(.username)
|
||||
.autocorrectionDisabled()
|
||||
}
|
||||
LabeledContent(String(localized: "sources.field.password")) {
|
||||
SecureField("", text: $basicAuthPassword)
|
||||
.textContentType(.password)
|
||||
}
|
||||
#else
|
||||
TextField(String(localized: "sources.field.username"), text: $basicAuthUsername)
|
||||
.textContentType(.username)
|
||||
|
||||
@@ -39,6 +39,9 @@ struct AddSMBView: View {
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
macOSBody
|
||||
#else
|
||||
Form {
|
||||
nameSection
|
||||
serverSection
|
||||
@@ -68,8 +71,100 @@ struct AddSMBView: View {
|
||||
name = prefillName
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
private var macOSBody: some View {
|
||||
Form {
|
||||
Section {
|
||||
LabeledContent(String(localized: "sources.field.name")) {
|
||||
TextField("", text: $name)
|
||||
}
|
||||
} footer: {
|
||||
Text(String(localized: "sources.footer.displayName"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
LabeledContent(String(localized: "sources.placeholder.smbServer")) {
|
||||
TextField("", text: $server)
|
||||
.autocorrectionDisabled()
|
||||
}
|
||||
} footer: {
|
||||
Text(String(localized: "sources.footer.smb"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
LabeledContent(String(localized: "sources.field.usernameOptional")) {
|
||||
TextField("", text: $username)
|
||||
.textContentType(.username)
|
||||
.autocorrectionDisabled()
|
||||
}
|
||||
LabeledContent(String(localized: "sources.field.passwordOptional")) {
|
||||
SecureField("", text: $password)
|
||||
.textContentType(.password)
|
||||
}
|
||||
} header: {
|
||||
Text(String(localized: "sources.header.auth"))
|
||||
} footer: {
|
||||
Text(String(localized: "sources.footer.auth"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker(String(localized: "sources.field.smbProtocol"), selection: $protocolVersion) {
|
||||
ForEach(SMBProtocol.allCases, id: \.self) { proto in
|
||||
Text(proto.displayName).tag(proto)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text(String(localized: "sources.header.advanced"))
|
||||
} footer: {
|
||||
Text(String(localized: "sources.footer.smbProtocol"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
if let result = testResult {
|
||||
SourceTestResultSection(result: result)
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle(String(localized: "sources.addSMB"))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button {
|
||||
addSource()
|
||||
} label: {
|
||||
if isTesting {
|
||||
HStack(spacing: 6) {
|
||||
ProgressView().controlSize(.small)
|
||||
Text(testProgress ?? String(localized: "sources.testing"))
|
||||
}
|
||||
} else {
|
||||
Text(String(localized: "sources.addSource"))
|
||||
}
|
||||
}
|
||||
.disabled(!canAdd || isTesting)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if let prefillServer {
|
||||
server = prefillServer
|
||||
}
|
||||
if let prefillName, name.isEmpty {
|
||||
name = prefillName
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: - Sections
|
||||
|
||||
private var nameSection: some View {
|
||||
|
||||
@@ -40,6 +40,9 @@ struct AddWebDAVView: View {
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
macOSBody
|
||||
#else
|
||||
Form {
|
||||
nameSection
|
||||
serverSection
|
||||
@@ -72,8 +75,100 @@ struct AddWebDAVView: View {
|
||||
allowInvalidCertificates = true
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
private var macOSBody: some View {
|
||||
Form {
|
||||
Section {
|
||||
LabeledContent(String(localized: "sources.field.name")) {
|
||||
TextField("", text: $name)
|
||||
}
|
||||
} footer: {
|
||||
Text(String(localized: "sources.footer.displayName"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
LabeledContent(String(localized: "sources.placeholder.webdavUrl")) {
|
||||
TextField("", text: $urlString)
|
||||
.textContentType(.URL)
|
||||
.autocorrectionDisabled()
|
||||
}
|
||||
} footer: {
|
||||
Text(String(localized: "sources.footer.webdav"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
LabeledContent(String(localized: "sources.field.usernameOptional")) {
|
||||
TextField("", text: $username)
|
||||
.textContentType(.username)
|
||||
.autocorrectionDisabled()
|
||||
}
|
||||
LabeledContent(String(localized: "sources.field.passwordOptional")) {
|
||||
SecureField("", text: $password)
|
||||
.textContentType(.password)
|
||||
}
|
||||
} header: {
|
||||
Text(String(localized: "sources.header.auth"))
|
||||
} footer: {
|
||||
Text(String(localized: "sources.footer.auth"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
Toggle(String(localized: "sources.field.allowInvalidCertificates"), isOn: $allowInvalidCertificates)
|
||||
} header: {
|
||||
Text(String(localized: "sources.header.security"))
|
||||
} footer: {
|
||||
Text(String(localized: "sources.footer.allowInvalidCertificates"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
if let result = testResult {
|
||||
SourceTestResultSection(result: result)
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle(String(localized: "sources.addWebDAV"))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button {
|
||||
addSource()
|
||||
} label: {
|
||||
if isTesting {
|
||||
HStack(spacing: 6) {
|
||||
ProgressView().controlSize(.small)
|
||||
Text(testProgress ?? String(localized: "sources.testing"))
|
||||
}
|
||||
} else {
|
||||
Text(String(localized: "sources.addSource"))
|
||||
}
|
||||
}
|
||||
.disabled(!canAdd || isTesting)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if let url = prefillURL {
|
||||
urlString = url.absoluteString
|
||||
}
|
||||
if let prefillName, name.isEmpty {
|
||||
name = prefillName
|
||||
}
|
||||
if prefillAllowInvalidCertificates {
|
||||
allowInvalidCertificates = true
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: - Sections
|
||||
|
||||
private var nameSection: some View {
|
||||
|
||||
Reference in New Issue
Block a user