mirror of
https://github.com/yattee/yattee.git
synced 2026-05-13 02:45: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 {
|
||||
|
||||
@@ -89,7 +89,7 @@ struct AddSourceView: View {
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
.frame(minWidth: 500, minHeight: 450)
|
||||
.frame(minWidth: 560, minHeight: 560)
|
||||
#endif
|
||||
.sheet(isPresented: $showingNetworkDiscovery) {
|
||||
NetworkShareDiscoverySheet(filterType: selectedShareType) { share in
|
||||
|
||||
@@ -118,6 +118,11 @@ private struct EditRemoteServerContent: View {
|
||||
#if os(tvOS)
|
||||
TVSettingsTextField(title: String(localized: "sources.field.name"), text: $name)
|
||||
TVSettingsToggle(title: String(localized: "sources.field.enabled"), isOn: $isEnabled)
|
||||
#elseif os(macOS)
|
||||
LabeledContent(String(localized: "sources.field.name")) {
|
||||
TextField("", text: $name)
|
||||
}
|
||||
Toggle(String(localized: "sources.field.enabled"), isOn: $isEnabled)
|
||||
#else
|
||||
TextField(String(localized: "sources.field.name"), text: $name)
|
||||
Toggle(String(localized: "sources.field.enabled"), isOn: $isEnabled)
|
||||
@@ -128,6 +133,16 @@ private struct EditRemoteServerContent: 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)
|
||||
@@ -321,6 +336,9 @@ private struct EditRemoteServerContent: View {
|
||||
#if os(iOS)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
#endif
|
||||
#if os(macOS)
|
||||
.formStyle(.grouped)
|
||||
#endif
|
||||
.confirmationDialog(
|
||||
String(localized: "sources.delete.confirmation.single \(instance.displayName)"),
|
||||
isPresented: $showingDeleteConfirmation,
|
||||
@@ -564,6 +582,11 @@ private struct EditFileSourceContent: View {
|
||||
#if os(tvOS)
|
||||
TVSettingsTextField(title: String(localized: "sources.field.name"), text: $name)
|
||||
TVSettingsToggle(title: String(localized: "sources.field.enabled"), isOn: $isEnabled)
|
||||
#elseif os(macOS)
|
||||
LabeledContent(String(localized: "sources.field.name")) {
|
||||
TextField("", text: $name)
|
||||
}
|
||||
Toggle(String(localized: "sources.field.enabled"), isOn: $isEnabled)
|
||||
#else
|
||||
TextField(String(localized: "sources.field.name"), text: $name)
|
||||
Toggle(String(localized: "sources.field.enabled"), isOn: $isEnabled)
|
||||
@@ -615,6 +638,21 @@ private struct EditFileSourceContent: View {
|
||||
text: $password,
|
||||
isSecure: true
|
||||
)
|
||||
#elseif os(macOS)
|
||||
LabeledContent(String(localized: "sources.field.username")) {
|
||||
TextField("", text: $username)
|
||||
.textContentType(.username)
|
||||
.autocorrectionDisabled()
|
||||
}
|
||||
LabeledContent(String(localized: "sources.field.password")) {
|
||||
SecureField(
|
||||
hasExistingPassword
|
||||
? String(localized: "sources.field.passwordKeep")
|
||||
: String(localized: "sources.field.passwordRequired"),
|
||||
text: $password
|
||||
)
|
||||
.textContentType(.password)
|
||||
}
|
||||
#else
|
||||
TextField(String(localized: "sources.field.username"), text: $username)
|
||||
.textContentType(.username)
|
||||
@@ -718,6 +756,9 @@ private struct EditFileSourceContent: View {
|
||||
#if os(iOS)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
#endif
|
||||
#if os(macOS)
|
||||
.formStyle(.grouped)
|
||||
#endif
|
||||
.confirmationDialog(
|
||||
String(localized: "sources.delete.confirmation.single \(source.name)"),
|
||||
isPresented: $showingDeleteConfirmation,
|
||||
|
||||
Reference in New Issue
Block a user