Improve UI
This commit is contained in:
@@ -5,10 +5,6 @@
|
||||
|
||||
@MainActor
|
||||
public struct EditProfileContentView: View {
|
||||
#if os(macOS)
|
||||
public static let windowID = "edit-profile-content"
|
||||
#endif
|
||||
|
||||
public struct Context: Codable, Hashable {
|
||||
public let profileID: Int64
|
||||
public let readOnly: Bool
|
||||
|
||||
@@ -4,10 +4,6 @@ import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public struct EditProfileView: View {
|
||||
#if os(macOS)
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
#endif
|
||||
|
||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@EnvironmentObject private var profile: Profile
|
||||
@@ -58,39 +54,45 @@ public struct EditProfileView: View {
|
||||
FormTextItem("Last Updated", profile.lastUpdatedString)
|
||||
}
|
||||
}
|
||||
#if os(iOS) || os(tvOS)
|
||||
Section("Action") {
|
||||
if profile.type != .remote {
|
||||
#if os(iOS)
|
||||
NavigationLink {
|
||||
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: false))
|
||||
} label: {
|
||||
Text("Edit Content").foregroundColor(.accentColor)
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#if os(iOS)
|
||||
NavigationLink {
|
||||
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: true))
|
||||
} label: {
|
||||
Text("View Content").foregroundColor(.accentColor)
|
||||
}
|
||||
#endif
|
||||
Button("Update") {
|
||||
isLoading = true
|
||||
Task {
|
||||
await updateProfile()
|
||||
}
|
||||
Section("Action") {
|
||||
if profile.type != .remote {
|
||||
#if os(iOS) || os(macOS)
|
||||
NavigationLink {
|
||||
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: false))
|
||||
} label: {
|
||||
Label("Edit Content", systemImage: "pencil")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
.disabled(isLoading)
|
||||
}
|
||||
Button("Delete", role: .destructive) {
|
||||
#endif
|
||||
} else {
|
||||
#if os(iOS) || os(macOS)
|
||||
NavigationLink {
|
||||
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: true))
|
||||
} label: {
|
||||
Label("View Content", systemImage: "doc.fill")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
#endif
|
||||
FormButton {
|
||||
isLoading = true
|
||||
Task {
|
||||
await deleteProfile()
|
||||
await updateProfile()
|
||||
}
|
||||
} label: {
|
||||
Label("Update", systemImage: "arrow.clockwise")
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
.disabled(isLoading)
|
||||
}
|
||||
#endif
|
||||
FormButton(role: .destructive) {
|
||||
Task {
|
||||
await deleteProfile()
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash.fill")
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
.onChangeCompat(of: profile.name) {
|
||||
isChanged = true
|
||||
@@ -114,30 +116,6 @@ public struct EditProfileView: View {
|
||||
Image("save", bundle: ApplicationLibrary.bundle, label: Text("Save"))
|
||||
}
|
||||
.disabled(isLoading || !isChanged)
|
||||
if profile.type != .remote {
|
||||
Button {
|
||||
openWindow(id: EditProfileContentView.windowID, value: EditProfileContentView.Context(profileID: profile.id!, readOnly: false))
|
||||
} label: {
|
||||
Label("Edit Content", systemImage: "pencil")
|
||||
}
|
||||
.disabled(isLoading)
|
||||
} else {
|
||||
Button {
|
||||
isLoading = true
|
||||
Task {
|
||||
await updateProfile()
|
||||
}
|
||||
} label: {
|
||||
Label("Update", systemImage: "arrow.clockwise")
|
||||
}
|
||||
.disabled(isLoading)
|
||||
Button {
|
||||
openWindow(id: EditProfileContentView.windowID, value: EditProfileContentView.Context(profileID: profile.id!, readOnly: true))
|
||||
} label: {
|
||||
Label("View Content", systemImage: "doc.text.fill")
|
||||
}
|
||||
.disabled(isLoading)
|
||||
}
|
||||
}
|
||||
}
|
||||
#elseif os(iOS)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
#if os(macOS)
|
||||
@MainActor
|
||||
public struct EditProfileWindowView: View {
|
||||
public static let windowID = "edit-profile"
|
||||
|
||||
private var profileID: Int64?
|
||||
|
||||
public init(_ profileID: Int64?) {
|
||||
self.profileID = profileID
|
||||
}
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var isLoading = true
|
||||
@State private var profile: Profile!
|
||||
@State private var alert: Alert?
|
||||
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
if isLoading {
|
||||
ProgressView().onAppear {
|
||||
Task {
|
||||
await doReload()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EditProfileView().environmentObject(profile!)
|
||||
}
|
||||
}
|
||||
.alertBinding($alert)
|
||||
.onExitCommand {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private func doReload() async {
|
||||
guard let profileID else {
|
||||
alert = Alert(errorMessage: "Context destroyed")
|
||||
return
|
||||
}
|
||||
do {
|
||||
profile = try await ProfileManager.get(profileID)
|
||||
} catch {
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
if profile == nil {
|
||||
alert = Alert(errorMessage: "Profile deleted")
|
||||
return
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -5,10 +5,6 @@ import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public struct NewProfileView: View {
|
||||
#if os(macOS)
|
||||
public static let windowID = "new-profile"
|
||||
#endif
|
||||
|
||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@@ -100,11 +96,13 @@ public struct NewProfileView: View {
|
||||
}
|
||||
Section {
|
||||
if !isSaving {
|
||||
Button("Create") {
|
||||
FormButton {
|
||||
isSaving = true
|
||||
Task {
|
||||
await createProfile()
|
||||
}
|
||||
} label: {
|
||||
Label("Create", systemImage: "doc.fill.badge.plus")
|
||||
}
|
||||
} else {
|
||||
ProgressView()
|
||||
|
||||
@@ -21,8 +21,6 @@ public struct ProfileView: View {
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
@State private var editMode = EditMode.inactive
|
||||
#elseif os(macOS)
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
#endif
|
||||
|
||||
#if os(tvOS)
|
||||
@@ -39,73 +37,67 @@ public struct ProfileView: View {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#if os(iOS) || os(tvOS)
|
||||
ZStack {
|
||||
if let importRemoteProfileRequest {
|
||||
NavigationDestinationCompat(isPresented: $importRemoteProfilePresented) {
|
||||
NewProfileView(importRemoteProfileRequest)
|
||||
}
|
||||
ZStack {
|
||||
if let importRemoteProfileRequest {
|
||||
NavigationDestinationCompat(isPresented: $importRemoteProfilePresented) {
|
||||
NewProfileView(importRemoteProfileRequest)
|
||||
}
|
||||
FormView {
|
||||
#if os(iOS)
|
||||
}
|
||||
FormView {
|
||||
#if os(iOS)
|
||||
NavigationLink {
|
||||
NewProfileView()
|
||||
} label: {
|
||||
Text("New Profile").foregroundColor(.accentColor)
|
||||
}
|
||||
.disabled(editMode.isEditing)
|
||||
#elseif os(macOS)
|
||||
NavigationLink {
|
||||
NewProfileView()
|
||||
} label: {
|
||||
Text("New Profile")
|
||||
}
|
||||
#elseif os(tvOS)
|
||||
Section {
|
||||
NavigationLink {
|
||||
NewProfileView()
|
||||
} label: {
|
||||
Text("New Profile").foregroundColor(.accentColor)
|
||||
}
|
||||
.disabled(editMode.isEditing)
|
||||
#elseif os(tvOS)
|
||||
Section {
|
||||
if ApplicationLibrary.inPreview || devicePickerSupports(.applicationService(name: "sing-box"), parameters: { .applicationService }) {
|
||||
NavigationLink {
|
||||
NewProfileView()
|
||||
} label: {
|
||||
Text("New Profile").foregroundColor(.accentColor)
|
||||
}
|
||||
if ApplicationLibrary.inPreview || devicePickerSupports(.applicationService(name: "sing-box"), parameters: { .applicationService }) {
|
||||
NavigationLink {
|
||||
ImportProfileView {
|
||||
await doReload()
|
||||
}
|
||||
} label: {
|
||||
Text("Import Profile").foregroundColor(.accentColor)
|
||||
ImportProfileView {
|
||||
await doReload()
|
||||
}
|
||||
} label: {
|
||||
Text("Import Profile").foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if profileList.isEmpty {
|
||||
Text("Empty profiles")
|
||||
} else {
|
||||
List {
|
||||
ForEach(profileList, id: \.id) { profile in
|
||||
viewBuilder {
|
||||
}
|
||||
#endif
|
||||
if profileList.isEmpty {
|
||||
Text("Empty profiles")
|
||||
} else {
|
||||
List {
|
||||
ForEach(profileList, id: \.id) { profile in
|
||||
viewBuilder {
|
||||
#if os(iOS) || os(tvOS)
|
||||
if editMode.isEditing == true {
|
||||
Text(profile.name)
|
||||
} else {
|
||||
ProfileItem(self, profile)
|
||||
}
|
||||
}
|
||||
#else
|
||||
ProfileItem(self, profile)
|
||||
#endif
|
||||
}
|
||||
.onMove(perform: moveProfile)
|
||||
.onDelete(perform: deleteProfile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#elseif os(macOS)
|
||||
if profileList.isEmpty {
|
||||
Text("Empty profiles")
|
||||
} else {
|
||||
FormView {
|
||||
List {
|
||||
ForEach(profileList, id: \.id) { profile in
|
||||
ProfileItem(self, profile)
|
||||
}
|
||||
.onMove(perform: moveProfile)
|
||||
.onDelete(perform: deleteProfile)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(isUpdating)
|
||||
@@ -140,17 +132,7 @@ public struct ProfileView: View {
|
||||
// await doReload()
|
||||
// }
|
||||
}
|
||||
#if os(macOS)
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button {
|
||||
openWindow(id: NewProfileView.windowID)
|
||||
} label: {
|
||||
Label("New Profile", systemImage: "plus.square.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
#elseif os(iOS)
|
||||
#if os(iOS)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
EditButton().disabled(profileList.isEmpty)
|
||||
@@ -202,11 +184,7 @@ public struct ProfileView: View {
|
||||
title: Text("Import Remote Profile"),
|
||||
message: Text("Are you sure to import remote profile \(newValue.name)? You will connect to \(newValue.host) to download the configuration."),
|
||||
primaryButton: .default(Text("Import")) {
|
||||
#if os(iOS) || os(tvOS)
|
||||
importRemoteProfilePresented = true
|
||||
#elseif os(macOS)
|
||||
openWindow(id: NewProfileView.windowID, value: importRemoteProfileRequest!)
|
||||
#endif
|
||||
importRemoteProfilePresented = true
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
@@ -285,6 +263,7 @@ public struct ProfileView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public struct ProfileItem: View {
|
||||
private let parent: ProfileView
|
||||
@State private var profile: ProfilePreview
|
||||
@@ -307,7 +286,6 @@ public struct ProfileView: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private var body0: some View {
|
||||
viewBuilder {
|
||||
#if !os(macOS)
|
||||
@@ -346,57 +324,63 @@ public struct ProfileView: View {
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash.fill")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
#else
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text(profile.name)
|
||||
if profile.type == .remote {
|
||||
Spacer(minLength: 4)
|
||||
Text("Last Updated: \(profile.origin.lastUpdatedString)").font(.caption)
|
||||
}
|
||||
}
|
||||
NavigationLink {
|
||||
EditProfileView().environmentObject(profile.origin)
|
||||
} label: {
|
||||
HStack {
|
||||
if profile.type == .remote {
|
||||
VStack(alignment: .leading) {
|
||||
Text(profile.name)
|
||||
if profile.type == .remote {
|
||||
Spacer(minLength: 4)
|
||||
Text("Last Updated: \(profile.origin.lastUpdatedString)").font(.caption)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
if profile.type == .remote {
|
||||
Button {
|
||||
parent.isUpdating = true
|
||||
Task {
|
||||
await parent.updateProfile(profile.origin)
|
||||
profile = ProfilePreview(profile.origin)
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
}
|
||||
.padding(.leading, 4)
|
||||
|
||||
Button {
|
||||
shareLinkPresented = true
|
||||
} label: {
|
||||
Image(systemName: "qrcode")
|
||||
}
|
||||
.padding(.leading, 4)
|
||||
.popover(isPresented: $shareLinkPresented, arrowEdge: .bottom) {
|
||||
shareLinkView
|
||||
}
|
||||
}
|
||||
ProfileShareButton(parent.$alert, profile.origin) {
|
||||
Image(systemName: "square.and.arrow.up.fill")
|
||||
}
|
||||
.padding(.leading, 4)
|
||||
Button {
|
||||
parent.isUpdating = true
|
||||
Task {
|
||||
await parent.updateProfile(profile.origin)
|
||||
profile = ProfilePreview(profile.origin)
|
||||
await parent.deleteProfile(profile.origin)
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
}
|
||||
Button {
|
||||
shareLinkPresented = true
|
||||
} label: {
|
||||
Image(systemName: "qrcode")
|
||||
}
|
||||
.popover(isPresented: $shareLinkPresented, arrowEdge: .bottom) {
|
||||
shareLinkView
|
||||
Image(systemName: "trash.fill")
|
||||
}
|
||||
.padding([.leading, .trailing], 4)
|
||||
}
|
||||
ProfileShareButton(parent.$alert, profile.origin) {
|
||||
Image(systemName: "square.and.arrow.up.fill")
|
||||
}
|
||||
Button {
|
||||
parent.openWindow(id: EditProfileWindowView.windowID, value: profile.id)
|
||||
} label: {
|
||||
Image(systemName: "pencil")
|
||||
}
|
||||
Button {
|
||||
Task {
|
||||
await parent.deleteProfile(profile.origin)
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "trash.fill")
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.padding(.vertical, 8)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -412,6 +396,9 @@ public struct ProfileView: View {
|
||||
shareLinkView0
|
||||
}
|
||||
}
|
||||
#elseif os(macOS)
|
||||
shareLinkView0
|
||||
.frame(minWidth: 300, minHeight: 300)
|
||||
#else
|
||||
shareLinkView0
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user