Fix interfaces for tvOS
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct BackButton: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
Button {
|
||||||
|
dismiss()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "chevron.backward")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,3 +83,18 @@ public func FormButton(role: ButtonRole?, action: @escaping () -> Void, @ViewBui
|
|||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func FormNavigationLink(@ViewBuilder destination: () -> some View, @ViewBuilder label: () -> some View) -> some View {
|
||||||
|
#if !os(tvOS)
|
||||||
|
return NavigationLink(destination: destination, label: label)
|
||||||
|
#else
|
||||||
|
return NavigationLink(destination: {
|
||||||
|
destination()
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItemGroup(placement: .topBarLeading) {
|
||||||
|
BackButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, label: label)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public struct EditProfileView: View {
|
|||||||
Section("Action") {
|
Section("Action") {
|
||||||
if profile.type != .remote {
|
if profile.type != .remote {
|
||||||
#if os(iOS) || os(macOS)
|
#if os(iOS) || os(macOS)
|
||||||
NavigationLink {
|
FormNavigationLink {
|
||||||
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: false))
|
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: false))
|
||||||
} label: {
|
} label: {
|
||||||
Label("Edit Content", systemImage: "pencil")
|
Label("Edit Content", systemImage: "pencil")
|
||||||
@@ -66,7 +66,7 @@ public struct EditProfileView: View {
|
|||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
#if os(iOS) || os(macOS)
|
#if os(iOS) || os(macOS)
|
||||||
NavigationLink {
|
FormNavigationLink {
|
||||||
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: true))
|
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: true))
|
||||||
} label: {
|
} label: {
|
||||||
Label("View Content", systemImage: "doc.fill")
|
Label("View Content", systemImage: "doc.fill")
|
||||||
|
|||||||
@@ -22,25 +22,37 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
VStack {
|
VStack(alignment: .center) {
|
||||||
if !selected {
|
if !selected {
|
||||||
DevicePicker(
|
Form {
|
||||||
.applicationService(name: "sing-box:profile"))
|
Section {
|
||||||
{ endpoint in
|
EmptyView()
|
||||||
selected = true
|
} footer: {
|
||||||
Task {
|
Text("To import configurations from your iPhone or iPad, make sure sing-box is the **same version** on both devices and **VPN is disabled**.")
|
||||||
await handleEndpoint(endpoint)
|
}
|
||||||
|
|
||||||
|
DevicePicker(
|
||||||
|
.applicationService(name: "sing-box:profile"))
|
||||||
|
{ endpoint in
|
||||||
|
selected = true
|
||||||
|
Task {
|
||||||
|
await handleEndpoint(endpoint)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text("Select Device")
|
||||||
|
} fallback: {
|
||||||
|
EmptyView()
|
||||||
|
} parameters: {
|
||||||
|
.applicationService
|
||||||
}
|
}
|
||||||
} label: {
|
|
||||||
Text("Select Device")
|
|
||||||
} fallback: {
|
|
||||||
EmptyView()
|
|
||||||
} parameters: {
|
|
||||||
.applicationService
|
|
||||||
}
|
}
|
||||||
} else if let profiles {
|
} else if let profiles {
|
||||||
Form {
|
Form {
|
||||||
Text("\(profiles.count) Profiles")
|
Section {
|
||||||
|
EmptyView()
|
||||||
|
} footer: {
|
||||||
|
Text("\(profiles.count) Profiles")
|
||||||
|
}
|
||||||
ForEach(profiles, id: \.profileID) { profile in
|
ForEach(profiles, id: \.profileID) { profile in
|
||||||
Button(profile.name) {
|
Button(profile.name) {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|||||||
@@ -45,27 +45,27 @@ public struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
FormView {
|
FormView {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
NavigationLink {
|
FormNavigationLink {
|
||||||
NewProfileView()
|
NewProfileView()
|
||||||
} label: {
|
} label: {
|
||||||
Text("New Profile").foregroundColor(.accentColor)
|
Text("New Profile").foregroundColor(.accentColor)
|
||||||
}
|
}
|
||||||
.disabled(editMode.isEditing)
|
.disabled(editMode.isEditing)
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
NavigationLink {
|
FormNavigationLink {
|
||||||
NewProfileView()
|
NewProfileView()
|
||||||
} label: {
|
} label: {
|
||||||
Text("New Profile")
|
Text("New Profile")
|
||||||
}
|
}
|
||||||
#elseif os(tvOS)
|
#elseif os(tvOS)
|
||||||
Section {
|
Section {
|
||||||
NavigationLink {
|
FormNavigationLink {
|
||||||
NewProfileView()
|
NewProfileView()
|
||||||
} label: {
|
} label: {
|
||||||
Text("New Profile").foregroundColor(.accentColor)
|
Text("New Profile").foregroundColor(.accentColor)
|
||||||
}
|
}
|
||||||
if ApplicationLibrary.inPreview || devicePickerSupports(.applicationService(name: "sing-box"), parameters: { .applicationService }) {
|
if ApplicationLibrary.inPreview || devicePickerSupports(.applicationService(name: "sing-box"), parameters: { .applicationService }) {
|
||||||
NavigationLink {
|
FormNavigationLink {
|
||||||
ImportProfileView {
|
ImportProfileView {
|
||||||
await doReload()
|
await doReload()
|
||||||
}
|
}
|
||||||
@@ -291,7 +291,7 @@ public struct ProfileView: View {
|
|||||||
private var body0: some View {
|
private var body0: some View {
|
||||||
viewBuilder {
|
viewBuilder {
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
NavigationLink {
|
FormNavigationLink {
|
||||||
EditProfileView().environmentObject(profile.origin)
|
EditProfileView().environmentObject(profile.origin)
|
||||||
} label: {
|
} label: {
|
||||||
Text(profile.name)
|
Text(profile.name)
|
||||||
@@ -329,7 +329,7 @@ public struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
NavigationLink {
|
FormNavigationLink {
|
||||||
EditProfileView().environmentObject(profile.origin)
|
EditProfileView().environmentObject(profile.origin)
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
|
|||||||
@@ -35,24 +35,28 @@ public struct ServiceLogView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !os(tvOS)
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
if !content.isEmpty {
|
if !content.isEmpty {
|
||||||
ShareButtonCompat($alert) {
|
#if !os(tvOS)
|
||||||
Label("Export", systemImage: "square.and.arrow.up.fill")
|
ShareButtonCompat($alert) {
|
||||||
} itemURL: {
|
Label("Export", systemImage: "square.and.arrow.up.fill")
|
||||||
try content.generateShareFile(name: "service.log")
|
} itemURL: {
|
||||||
}
|
try content.generateShareFile(name: "service.log")
|
||||||
|
}
|
||||||
|
#endif
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
Task {
|
Task {
|
||||||
await deleteContent()
|
await deleteContent()
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("Delete", systemImage: "trash.fill")
|
#if !os(tvOS)
|
||||||
|
Label("Delete", systemImage: "trash.fill")
|
||||||
|
#else
|
||||||
|
Image(systemName: "trash.fill")
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
.alertBinding($alert)
|
.alertBinding($alert)
|
||||||
.navigationTitle("Service Log")
|
.navigationTitle("Service Log")
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public struct SettingView: View {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
var navigationLink: some View {
|
var navigationLink: some View {
|
||||||
NavigationLink {
|
FormNavigationLink {
|
||||||
contentView
|
contentView
|
||||||
} label: {
|
} label: {
|
||||||
label
|
label
|
||||||
@@ -98,25 +98,25 @@ public struct SettingView: View {
|
|||||||
ForEach([Tabs.core, Tabs.packetTunnel, Tabs.profileOverride]) { it in
|
ForEach([Tabs.core, Tabs.packetTunnel, Tabs.profileOverride]) { it in
|
||||||
it.navigationLink
|
it.navigationLink
|
||||||
}
|
}
|
||||||
Section("About") {
|
#if !os(tvOS)
|
||||||
Link(destination: URL(string: "https://sing-box.sagernet.org/")!) {
|
Section("About") {
|
||||||
Label("Documentation", systemImage: "doc.on.doc.fill")
|
Link(destination: URL(string: "https://sing-box.sagernet.org/")!) {
|
||||||
}
|
Label("Documentation", systemImage: "doc.on.doc.fill")
|
||||||
.buttonStyle(.plain)
|
}
|
||||||
.foregroundColor(.accentColor)
|
.buttonStyle(.plain)
|
||||||
#if !os(tvOS)
|
.foregroundColor(.accentColor)
|
||||||
RequestReviewButton {
|
RequestReviewButton {
|
||||||
Label("Rate on the App Store", systemImage: "text.bubble.fill")
|
Label("Rate on the App Store", systemImage: "text.bubble.fill")
|
||||||
}
|
}
|
||||||
#endif
|
#if os(macOS)
|
||||||
#if os(macOS)
|
if Variant.useSystemExtension {
|
||||||
if Variant.useSystemExtension {
|
Tabs.sponsors.navigationLink
|
||||||
Tabs.sponsors.navigationLink
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
#endif
|
||||||
Section("Debug") {
|
Section("Debug") {
|
||||||
NavigationLink {
|
FormNavigationLink {
|
||||||
ServiceLogView()
|
ServiceLogView()
|
||||||
} label: {
|
} label: {
|
||||||
Label("Service Log", systemImage: "doc.on.clipboard")
|
Label("Service Log", systemImage: "doc.on.clipboard")
|
||||||
@@ -136,6 +136,5 @@ public struct SettingView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Settings")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
3A096F8F2A4ED3DE00D4A2ED /* Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3A096F862A4ED3DE00D4A2ED /* Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
3A096F8F2A4ED3DE00D4A2ED /* Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3A096F862A4ED3DE00D4A2ED /* Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
3A0C6D3C2A79D46500A4DF2B /* OverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0C6D3B2A79D46500A4DF2B /* OverviewView.swift */; };
|
3A0C6D3C2A79D46500A4DF2B /* OverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0C6D3B2A79D46500A4DF2B /* OverviewView.swift */; };
|
||||||
3A0C6D3E2A79D6A600A4DF2B /* DashboardPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0C6D3D2A79D6A600A4DF2B /* DashboardPage.swift */; };
|
3A0C6D3E2A79D6A600A4DF2B /* DashboardPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0C6D3D2A79D6A600A4DF2B /* DashboardPage.swift */; };
|
||||||
|
3A172D2B2B88E9DB00D98050 /* BackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A172D2A2B88E9DB00D98050 /* BackButton.swift */; };
|
||||||
3A1CF2F02A50E5EE000A8289 /* GroupListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1CF2EF2A50E5EE000A8289 /* GroupListView.swift */; };
|
3A1CF2F02A50E5EE000A8289 /* GroupListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1CF2EF2A50E5EE000A8289 /* GroupListView.swift */; };
|
||||||
3A1CF2F22A50E613000A8289 /* OutboundGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1CF2F12A50E613000A8289 /* OutboundGroup.swift */; };
|
3A1CF2F22A50E613000A8289 /* OutboundGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1CF2F12A50E613000A8289 /* OutboundGroup.swift */; };
|
||||||
3A1CF2F62A50EE9C000A8289 /* GroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1CF2F52A50EE9C000A8289 /* GroupView.swift */; };
|
3A1CF2F62A50EE9C000A8289 /* GroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1CF2F52A50EE9C000A8289 /* GroupView.swift */; };
|
||||||
@@ -439,6 +440,7 @@
|
|||||||
3A096F8C2A4ED3DE00D4A2ED /* Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Extension.entitlements; sourceTree = "<group>"; };
|
3A096F8C2A4ED3DE00D4A2ED /* Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Extension.entitlements; sourceTree = "<group>"; };
|
||||||
3A0C6D3B2A79D46500A4DF2B /* OverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewView.swift; sourceTree = "<group>"; };
|
3A0C6D3B2A79D46500A4DF2B /* OverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewView.swift; sourceTree = "<group>"; };
|
||||||
3A0C6D3D2A79D6A600A4DF2B /* DashboardPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardPage.swift; sourceTree = "<group>"; };
|
3A0C6D3D2A79D6A600A4DF2B /* DashboardPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardPage.swift; sourceTree = "<group>"; };
|
||||||
|
3A172D2A2B88E9DB00D98050 /* BackButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackButton.swift; sourceTree = "<group>"; };
|
||||||
3A1CF2EF2A50E5EE000A8289 /* GroupListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupListView.swift; sourceTree = "<group>"; };
|
3A1CF2EF2A50E5EE000A8289 /* GroupListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupListView.swift; sourceTree = "<group>"; };
|
||||||
3A1CF2F12A50E613000A8289 /* OutboundGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboundGroup.swift; sourceTree = "<group>"; };
|
3A1CF2F12A50E613000A8289 /* OutboundGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboundGroup.swift; sourceTree = "<group>"; };
|
||||||
3A1CF2F32A50E937000A8289 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; };
|
3A1CF2F32A50E937000A8289 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; };
|
||||||
@@ -1018,6 +1020,7 @@
|
|||||||
3ACE6DE22ACADF55009D9A8A /* Binding+Setter.swift */,
|
3ACE6DE22ACADF55009D9A8A /* Binding+Setter.swift */,
|
||||||
3A3AB2A82B70C5F1001815AE /* RequestReviewButton.swift */,
|
3A3AB2A82B70C5F1001815AE /* RequestReviewButton.swift */,
|
||||||
3ACA8B322B7E037800B7238F /* DeleteButton.swift */,
|
3ACA8B322B7E037800B7238F /* DeleteButton.swift */,
|
||||||
|
3A172D2A2B88E9DB00D98050 /* BackButton.swift */,
|
||||||
);
|
);
|
||||||
path = Abstract;
|
path = Abstract;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1477,6 +1480,7 @@
|
|||||||
3AF5E3E72B6F90640058B9E8 /* ProfileOverrideView.swift in Sources */,
|
3AF5E3E72B6F90640058B9E8 /* ProfileOverrideView.swift in Sources */,
|
||||||
3A4EAD302A4FEB77005435B3 /* NewProfileView.swift in Sources */,
|
3A4EAD302A4FEB77005435B3 /* NewProfileView.swift in Sources */,
|
||||||
3A3AB2A72B70C146001815AE /* CoreView.swift in Sources */,
|
3A3AB2A72B70C146001815AE /* CoreView.swift in Sources */,
|
||||||
|
3A172D2B2B88E9DB00D98050 /* BackButton.swift in Sources */,
|
||||||
3A4EAD242A4FEB65005435B3 /* InstallProfileButton.swift in Sources */,
|
3A4EAD242A4FEB65005435B3 /* InstallProfileButton.swift in Sources */,
|
||||||
3AE4D0C12A6E4852009FEA9E /* InstallSystemExtensionButton.swift in Sources */,
|
3AE4D0C12A6E4852009FEA9E /* InstallSystemExtensionButton.swift in Sources */,
|
||||||
3A4EAD232A4FEB5A005435B3 /* EnvironmentValues.swift in Sources */,
|
3A4EAD232A4FEB5A005435B3 /* EnvironmentValues.swift in Sources */,
|
||||||
|
|||||||
Reference in New Issue
Block a user