Add swiftlint & Minor fixes
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
excluded:
|
||||
- Frameworks
|
||||
- Libbox.xcframework
|
||||
- build
|
||||
|
||||
disabled_rules:
|
||||
- identifier_name
|
||||
- type_body_length
|
||||
- file_length
|
||||
- function_body_length
|
||||
- cyclomatic_complexity
|
||||
- large_tuple
|
||||
- nesting
|
||||
- line_length
|
||||
- todo
|
||||
- trailing_comma
|
||||
- function_parameter_count
|
||||
- type_name
|
||||
- force_cast
|
||||
- force_try
|
||||
- opening_brace
|
||||
@@ -27,20 +27,6 @@ public struct NavigationButtonsView: View {
|
||||
|
||||
public var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
if showGroupsButton {
|
||||
Divider()
|
||||
Text(verbatim: "\(groupsCount)")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
Button {
|
||||
onGroupsTap()
|
||||
} label: {
|
||||
Label("Groups", systemImage: "rectangle.3.group.fill")
|
||||
}
|
||||
.labelStyle(.iconOnly)
|
||||
.foregroundStyle(.primary)
|
||||
}
|
||||
if showConnectionsButton {
|
||||
Divider()
|
||||
Text(verbatim: "\(connectionsCount)")
|
||||
@@ -55,6 +41,20 @@ public struct NavigationButtonsView: View {
|
||||
.labelStyle(.iconOnly)
|
||||
.foregroundStyle(.primary)
|
||||
}
|
||||
if showGroupsButton {
|
||||
Divider()
|
||||
Text(verbatim: "\(groupsCount)")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
Button {
|
||||
onGroupsTap()
|
||||
} label: {
|
||||
Label("Groups", systemImage: "rectangle.3.group.fill")
|
||||
}
|
||||
.labelStyle(.iconOnly)
|
||||
.foregroundStyle(.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,12 @@ public struct Connection: Codable {
|
||||
public let outboundType: String
|
||||
public let chain: [String]
|
||||
|
||||
var hashValue: Int {
|
||||
var value = id.hashValue
|
||||
(value, _) = value.addingReportingOverflow(upload.hashValue)
|
||||
(value, _) = value.addingReportingOverflow(download.hashValue)
|
||||
(value, _) = value.addingReportingOverflow(uploadTotal.hashValue)
|
||||
(value, _) = value.addingReportingOverflow(downloadTotal.hashValue)
|
||||
return value
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
hasher.combine(upload)
|
||||
hasher.combine(download)
|
||||
hasher.combine(uploadTotal)
|
||||
hasher.combine(downloadTotal)
|
||||
}
|
||||
|
||||
func performSearch(_ content: String) -> Bool {
|
||||
|
||||
@@ -17,7 +17,7 @@ public struct ConnectionListView: View {
|
||||
} else {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [GridItem(.flexible())], alignment: .leading) {
|
||||
ForEach(viewModel.filteredConnections(), id: \.hashValue) { it in
|
||||
ForEach(viewModel.filteredConnections(), id: \.id) { it in
|
||||
ConnectionView(it)
|
||||
}
|
||||
}
|
||||
@@ -56,9 +56,19 @@ public struct ConnectionListView: View {
|
||||
#endif
|
||||
.alertBinding($viewModel.alert)
|
||||
.onAppear {
|
||||
viewModel.setCommandClient(environments.commandClient)
|
||||
viewModel.connect()
|
||||
}
|
||||
.onReceive(environments.commandClient.$connections) { connections in
|
||||
viewModel.setConnections(connections)
|
||||
}
|
||||
.onChangeCompat(of: viewModel.connectionStateFilter) { filter in
|
||||
environments.commandClient.connectionStateFilter = filter
|
||||
environments.commandClient.filterConnectionsNow()
|
||||
}
|
||||
.onChangeCompat(of: viewModel.connectionSort) { sort in
|
||||
environments.commandClient.connectionSort = sort
|
||||
environments.commandClient.filterConnectionsNow()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
||||
#if os(iOS)
|
||||
.background(Color(uiColor: .systemGroupedBackground))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Combine
|
||||
import Libbox
|
||||
import Library
|
||||
import SwiftUI
|
||||
@@ -11,8 +10,6 @@ public class ConnectionListViewModel: ObservableObject {
|
||||
@Published public var alert: Alert?
|
||||
@Published public var connectionStateFilter: ConnectionStateFilter {
|
||||
didSet {
|
||||
commandClient?.connectionStateFilter = connectionStateFilter
|
||||
commandClient?.filterConnectionsNow()
|
||||
saveStateFilterTask?.cancel()
|
||||
saveStateFilterTask = Task {
|
||||
await SharedPreferences.connectionStateFilter.set(connectionStateFilter.rawValue)
|
||||
@@ -22,8 +19,6 @@ public class ConnectionListViewModel: ObservableObject {
|
||||
|
||||
@Published public var connectionSort: ConnectionSort {
|
||||
didSet {
|
||||
commandClient?.connectionSort = connectionSort
|
||||
commandClient?.filterConnectionsNow()
|
||||
saveSortTask?.cancel()
|
||||
saveSortTask = Task {
|
||||
await SharedPreferences.connectionSort.set(connectionSort.rawValue)
|
||||
@@ -31,8 +26,6 @@ public class ConnectionListViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private var commandClient: CommandClient?
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var connectTask: Task<Void, Never>?
|
||||
private var saveStateFilterTask: Task<Void, Never>?
|
||||
private var saveSortTask: Task<Void, Never>?
|
||||
@@ -42,16 +35,6 @@ public class ConnectionListViewModel: ObservableObject {
|
||||
connectionSort = .byDate
|
||||
}
|
||||
|
||||
public func setCommandClient(_ client: CommandClient) {
|
||||
commandClient = client
|
||||
client.$connections
|
||||
.compactMap { $0 }
|
||||
.sink { [weak self] goConnections in
|
||||
self?.setConnections(goConnections)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
public func connect() {
|
||||
if ApplicationLibrary.inPreview {
|
||||
isLoading = false
|
||||
@@ -97,7 +80,8 @@ public class ConnectionListViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func setConnections(_ goConnections: [LibboxConnection]) {
|
||||
public func setConnections(_ goConnections: [LibboxConnection]?) {
|
||||
guard let goConnections else { return }
|
||||
connections = convertConnections(goConnections)
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
@@ -111,22 +111,25 @@ public struct ActiveDashboardView: View {
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
private func updateButtonVisibility() {
|
||||
buttonState.update(profile: profile, commandClient: environments.commandClient)
|
||||
buttonState.update(profile: profile, commandClient: environments.commandClient, requireAnyConnection: true)
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
private var isTabViewBottomAccessoryAvailable: Bool {
|
||||
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
#endif
|
||||
|
||||
@ToolbarContentBuilder
|
||||
private var toolbar: some ToolbarContent {
|
||||
#if os(tvOS)
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
#if os(iOS)
|
||||
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
||||
EmptyView()
|
||||
} else {
|
||||
navigationButtons
|
||||
}
|
||||
#else
|
||||
navigationButtons
|
||||
#endif
|
||||
}
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
if #available(iOS 16.0, tvOS 17.0, *) {
|
||||
cardManagementButton
|
||||
@@ -137,11 +140,8 @@ public struct ActiveDashboardView: View {
|
||||
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
||||
EmptyView()
|
||||
} else {
|
||||
HStack(spacing: 12) {
|
||||
Divider()
|
||||
StartStopButton()
|
||||
}
|
||||
}
|
||||
#else
|
||||
HStack(spacing: 12) {
|
||||
Divider()
|
||||
@@ -151,6 +151,7 @@ public struct ActiveDashboardView: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
private var navigationButtons: some View {
|
||||
NavigationButtonsView(
|
||||
showGroupsButton: buttonState.showGroupsButton,
|
||||
@@ -162,6 +163,7 @@ public struct ActiveDashboardView: View {
|
||||
)
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
private var groupsSheetContent: some View {
|
||||
@@ -176,19 +178,42 @@ public struct ActiveDashboardView: View {
|
||||
@ViewBuilder
|
||||
private var cardManagementButton: some View {
|
||||
Menu {
|
||||
#if os(iOS)
|
||||
if !isTabViewBottomAccessoryAvailable {
|
||||
if buttonState.showGroupsButton {
|
||||
Button {
|
||||
showGroups = true
|
||||
} label: {
|
||||
Label("Groups (\(buttonState.groupsCount))", systemImage: "rectangle.3.group.fill")
|
||||
}
|
||||
}
|
||||
if buttonState.showConnectionsButton {
|
||||
Button {
|
||||
showConnections = true
|
||||
} label: {
|
||||
Label("Connections (\(buttonState.connectionsCount))", systemImage: "list.bullet.rectangle.portrait.fill")
|
||||
}
|
||||
}
|
||||
if buttonState.showGroupsButton || buttonState.showConnectionsButton {
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Button {
|
||||
showCardManagement = true
|
||||
} label: {
|
||||
Label("Dashboard Items", systemImage: "square.grid.2x2")
|
||||
}
|
||||
} label: {
|
||||
Label("Others", systemImage: "ellipsis.circle")
|
||||
Label("Others", systemImage: "line.3.horizontal.circle")
|
||||
}
|
||||
.sheet(isPresented: $showCardManagement) {
|
||||
CardManagementSheet(configurationVersion: $cardConfigurationVersion)
|
||||
.sheet(isPresented: $showCardManagement, onDismiss: {
|
||||
cardConfigurationVersion += 1
|
||||
}, content: {
|
||||
CardManagementSheet()
|
||||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
})
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -4,11 +4,8 @@ import SwiftUI
|
||||
@MainActor public struct CardManagementSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@StateObject private var configuration = DashboardCardConfiguration()
|
||||
@Binding private var configurationVersion: Int
|
||||
|
||||
public init(configurationVersion: Binding<Int>) {
|
||||
_configurationVersion = configurationVersion
|
||||
}
|
||||
public init() {}
|
||||
|
||||
public var body: some View {
|
||||
#if os(macOS)
|
||||
@@ -41,7 +38,6 @@ import SwiftUI
|
||||
Button("Reset", role: .destructive) {
|
||||
Task {
|
||||
await configuration.resetToDefault()
|
||||
configurationVersion += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,11 +45,9 @@ import SwiftUI
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
}
|
||||
.keyboardShortcut(.escape, modifiers: [])
|
||||
}
|
||||
}
|
||||
.onExitCommand {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
#else
|
||||
private var iOSBody: some View {
|
||||
@@ -74,7 +68,6 @@ import SwiftUI
|
||||
Button("Reset", role: .destructive) {
|
||||
Task {
|
||||
await configuration.resetToDefault()
|
||||
configurationVersion += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,13 +84,13 @@ import SwiftUI
|
||||
isEnabled: configuration.isEnabled(card),
|
||||
onToggle: {
|
||||
configuration.toggleCard(card)
|
||||
configurationVersion += 1
|
||||
}
|
||||
)
|
||||
}
|
||||
.onMove { source, destination in
|
||||
configuration.moveCard(from: source, to: destination)
|
||||
configurationVersion += 1
|
||||
Task {
|
||||
await configuration.moveCard(from: source, to: destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
.applyContentMargins()
|
||||
|
||||
@@ -40,14 +40,10 @@ public final class DashboardCardConfiguration: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
public func moveCard(from source: IndexSet, to destination: Int) {
|
||||
public func moveCard(from source: IndexSet, to destination: Int) async {
|
||||
cardOrder.move(fromOffsets: source, toOffset: destination)
|
||||
|
||||
// Save asynchronously in background
|
||||
Task {
|
||||
await saveCardOrder()
|
||||
}
|
||||
}
|
||||
|
||||
public func resetToDefault() async {
|
||||
await SharedPreferences.enabledDashboardCards.set([])
|
||||
|
||||
@@ -19,7 +19,11 @@ public struct HTTPProxyCard: View {
|
||||
|
||||
public var body: some View {
|
||||
DashboardCardView(title: "", isHalfWidth: false) {
|
||||
Toggle("System HTTP Proxy", isOn: $systemProxyEnabled)
|
||||
HStack {
|
||||
Text("System HTTP Proxy")
|
||||
Spacer()
|
||||
Toggle("", isOn: $systemProxyEnabled)
|
||||
.labelsHidden()
|
||||
.onChangeCompat(of: systemProxyEnabled) { newValue in
|
||||
Task {
|
||||
await onToggle(newValue)
|
||||
@@ -27,4 +31,5 @@ public struct HTTPProxyCard: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ public struct ProfileCard: View {
|
||||
.disabled(viewModel.isUpdating)
|
||||
.sheet(isPresented: $viewModel.showNewProfile, onDismiss: {
|
||||
environments.profileUpdate.send()
|
||||
}) {
|
||||
}, content: {
|
||||
NewProfileNavigationView()
|
||||
.environmentObject(environments)
|
||||
}
|
||||
})
|
||||
.sheet(isPresented: $viewModel.showManageProfiles) {
|
||||
manageProfilesSheet
|
||||
}
|
||||
@@ -218,11 +218,12 @@ public struct ProfileCard: View {
|
||||
NavigationSheet(
|
||||
title: String(localized: "Manage profiles"),
|
||||
showDoneButton: true,
|
||||
onDismiss: { viewModel.showManageProfiles = false }
|
||||
) {
|
||||
onDismiss: { viewModel.showManageProfiles = false },
|
||||
content: {
|
||||
ManageProfilesView()
|
||||
.environmentObject(environments)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -390,6 +391,7 @@ extension ProfileCard {
|
||||
@ObservedObject private var viewModel: ProfileViewModel
|
||||
@State private var profile: ProfilePreview
|
||||
@State private var shareLinkPresented = false
|
||||
@State private var isUpdating = false
|
||||
|
||||
init(_ viewModel: ProfileViewModel, _ profile: ProfilePreview) {
|
||||
self.viewModel = viewModel
|
||||
@@ -416,25 +418,26 @@ extension ProfileCard {
|
||||
HStack(spacing: 8) {
|
||||
if profile.type == .remote {
|
||||
Button {
|
||||
viewModel.isUpdating = true
|
||||
isUpdating = true
|
||||
Task {
|
||||
await viewModel.updateProfile(profile.origin)
|
||||
profile = ProfilePreview(profile.origin)
|
||||
isUpdating = false
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
.font(.system(size: 16))
|
||||
.rotationEffect(.degrees(viewModel.isUpdating ? 360 : 0))
|
||||
.rotationEffect(.degrees(isUpdating ? 360 : 0))
|
||||
.animation(
|
||||
viewModel.isUpdating
|
||||
isUpdating
|
||||
? .linear(duration: 1).repeatForever(autoreverses: false)
|
||||
: .default,
|
||||
value: viewModel.isUpdating
|
||||
value: isUpdating
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.actionButtonStyle()
|
||||
.disabled(viewModel.isUpdating)
|
||||
.disabled(isUpdating)
|
||||
|
||||
Button {
|
||||
shareLinkPresented = true
|
||||
|
||||
@@ -83,11 +83,9 @@ struct ProfileSelectorButton: View {
|
||||
|
||||
private func updateButtonContent(_ button: MenuAttachmentButton) {
|
||||
// Remove existing subviews
|
||||
for subview in button.subviews {
|
||||
if subview is UIStackView {
|
||||
for subview in button.subviews where subview is UIStackView {
|
||||
subview.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
// Create content stack
|
||||
let stackView = UIStackView()
|
||||
|
||||
@@ -42,12 +42,14 @@ public struct StartStopButton: View {
|
||||
await switchProfile(!profile.status.isConnected)
|
||||
}
|
||||
} label: {
|
||||
#if os(iOS)
|
||||
HStack(spacing: 8) {
|
||||
if profile.status.isConnectedStrict, let duration = runtimeDuration {
|
||||
if showRuntimeDuration, profile.status.isConnectedStrict, let duration = runtimeDuration {
|
||||
Text(duration)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.monospacedDigit()
|
||||
.fixedSize()
|
||||
.transition(.asymmetric(
|
||||
insertion: .move(edge: .trailing).combined(with: .opacity),
|
||||
removal: .move(edge: .trailing).combined(with: .opacity)
|
||||
@@ -61,9 +63,35 @@ public struct StartStopButton: View {
|
||||
}
|
||||
}
|
||||
.animation(.spring(response: 0.35, dampingFraction: 0.75), value: profile.status.isConnectedStrict)
|
||||
#else
|
||||
HStack(spacing: 8) {
|
||||
if profile.status.isConnectedStrict, let duration = runtimeDuration {
|
||||
Text(duration)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.monospacedDigit()
|
||||
.fixedSize()
|
||||
.transition(.asymmetric(
|
||||
insertion: .move(edge: .trailing).combined(with: .opacity),
|
||||
removal: .move(edge: .trailing).combined(with: .opacity)
|
||||
))
|
||||
}
|
||||
|
||||
if !profile.status.isConnected {
|
||||
Label("Start", systemImage: "play.fill")
|
||||
} else {
|
||||
Label("Stop", systemImage: "stop.fill")
|
||||
}
|
||||
}
|
||||
.animation(.spring(response: 0.35, dampingFraction: 0.75), value: profile.status.isConnectedStrict)
|
||||
#endif
|
||||
}
|
||||
.labelStyle(.iconOnly)
|
||||
#if os(iOS)
|
||||
.modifier(PrimaryTintModifier())
|
||||
#else
|
||||
.tint(.primary)
|
||||
#endif
|
||||
.disabled(!profile.status.isEnabled)
|
||||
.alertBinding($alert)
|
||||
.onReceive(timer) { _ in
|
||||
@@ -71,6 +99,15 @@ public struct StartStopButton: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
private var showRuntimeDuration: Bool {
|
||||
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
#endif
|
||||
|
||||
private var runtimeDuration: String? {
|
||||
guard let connectedDate = profile.connectedDate else { return nil }
|
||||
let interval = currentTime.timeIntervalSince(connectedDate)
|
||||
@@ -103,3 +140,15 @@ public struct StartStopButton: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
private struct PrimaryTintModifier: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
||||
content.tint(.primary)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -85,10 +85,10 @@ public struct DashboardView: View {
|
||||
private func importRemoteProfileSheet(for request: NewProfileView.ImportRequest) -> some View {
|
||||
NavigationSheet(title: "Import Profile", onDismiss: {
|
||||
environments.profileUpdate.send()
|
||||
}) {
|
||||
}, content: {
|
||||
NewProfileView(request)
|
||||
.environmentObject(environments)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -111,11 +111,11 @@ public struct DashboardView: View {
|
||||
@ViewBuilder
|
||||
private var mainContent: some View {
|
||||
if ApplicationLibrary.inPreview {
|
||||
ActiveDashboardView(externalCardConfigurationVersion: cardConfigurationVersion)
|
||||
activeDashboardView
|
||||
} else if environments.extensionProfileLoading {
|
||||
ProgressView()
|
||||
} else if let profile = environments.extensionProfile {
|
||||
ActiveDashboardView(externalCardConfigurationVersion: cardConfigurationVersion)
|
||||
activeDashboardView
|
||||
.environmentObject(profile)
|
||||
.alertBinding($coordinator.alert)
|
||||
.onChangeCompat(of: profile.status) { status in
|
||||
@@ -129,4 +129,13 @@ public struct DashboardView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var activeDashboardView: some View {
|
||||
#if os(macOS)
|
||||
ActiveDashboardView(externalCardConfigurationVersion: cardConfigurationVersion)
|
||||
#else
|
||||
ActiveDashboardView()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,10 @@ public struct GroupListView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.setCommandClient(environments.commandClient)
|
||||
viewModel.connect()
|
||||
}
|
||||
.onReceive(environments.commandClient.$groups) { groups in
|
||||
viewModel.setGroups(groups)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Combine
|
||||
import Libbox
|
||||
import Library
|
||||
import SwiftUI
|
||||
@@ -8,21 +7,8 @@ public class GroupListViewModel: ObservableObject {
|
||||
@Published public var isLoading = true
|
||||
@Published public var groups: [OutboundGroup] = []
|
||||
|
||||
private var commandClient: CommandClient?
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
public init() {}
|
||||
|
||||
public func setCommandClient(_ client: CommandClient) {
|
||||
commandClient = client
|
||||
client.$groups
|
||||
.compactMap { $0 }
|
||||
.sink { [weak self] goGroups in
|
||||
self?.setGroups(goGroups)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
public func connect() {
|
||||
if ApplicationLibrary.inPreview {
|
||||
groups = [
|
||||
@@ -40,7 +26,8 @@ public class GroupListViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func setGroups(_ goGroups: [LibboxOutboundGroup]) {
|
||||
public func setGroups(_ goGroups: [LibboxOutboundGroup]?) {
|
||||
guard let goGroups else { return }
|
||||
var groups = [OutboundGroup]()
|
||||
for goGroup in goGroups {
|
||||
var items = [OutboundGroupItem]()
|
||||
|
||||
@@ -2,7 +2,7 @@ import Foundation
|
||||
import Libbox
|
||||
import SwiftUI
|
||||
|
||||
public struct OutboundGroup: Codable {
|
||||
public struct OutboundGroup: Codable, Hashable {
|
||||
let tag: String
|
||||
let type: String
|
||||
var selected: String
|
||||
@@ -10,13 +10,16 @@ public struct OutboundGroup: Codable {
|
||||
var isExpand: Bool
|
||||
let items: [OutboundGroupItem]
|
||||
|
||||
var hashValue: Int {
|
||||
var value = tag.hashValue
|
||||
(value, _) = value.addingReportingOverflow(selected.hashValue)
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(tag)
|
||||
hasher.combine(selected)
|
||||
for item in items {
|
||||
(value, _) = value.addingReportingOverflow(item.urlTestTime.hashValue)
|
||||
hasher.combine(item.urlTestTime)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
public static func == (lhs: OutboundGroup, rhs: OutboundGroup) -> Bool {
|
||||
lhs.hashValue == rhs.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ private struct LogViewContent: View {
|
||||
.focusEffectDisabled()
|
||||
.focusSection()
|
||||
#else
|
||||
let previewLogs = logList.enumerated().map { _, message in
|
||||
let previewLogs = logList.map { message in
|
||||
LogEntry(level: 4, message: message)
|
||||
}
|
||||
return LogTextView(
|
||||
@@ -205,9 +205,9 @@ private struct LogViewContent: View {
|
||||
Button(action: {
|
||||
viewModel.prepareLogFile()
|
||||
viewModel.showFileExporter = true
|
||||
}) {
|
||||
}, label: {
|
||||
Label("To File", systemImage: "arrow.down.doc")
|
||||
}
|
||||
})
|
||||
Button(action: viewModel.prepareLogFile) {
|
||||
Label("Share", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
|
||||
@@ -18,14 +18,16 @@ public struct ProfileActionToolbar: View {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
#if os(iOS) || os(tvOS)
|
||||
#if os(iOS)
|
||||
iosBody
|
||||
#elseif os(tvOS)
|
||||
tvOSBody
|
||||
#elseif os(macOS)
|
||||
macOSBody
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
#if os(iOS)
|
||||
private var iosBody: some View {
|
||||
Section("Action") {
|
||||
if profile.type != .remote {
|
||||
@@ -65,6 +67,33 @@ public struct ProfileActionToolbar: View {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(tvOS)
|
||||
private var tvOSBody: some View {
|
||||
Section("Action") {
|
||||
if profile.type == .remote {
|
||||
FormButton {
|
||||
viewModel.isLoading = true
|
||||
Task {
|
||||
await viewModel.updateProfile(profile, environments: environments)
|
||||
}
|
||||
} label: {
|
||||
Label("Update", systemImage: "arrow.clockwise")
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
.disabled(viewModel.isLoading)
|
||||
}
|
||||
FormButton(role: .destructive) {
|
||||
Task {
|
||||
await viewModel.deleteProfile(profile, environments: environments, dismiss: dismiss)
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash.fill")
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
private var macOSBody: some View {
|
||||
VStack(spacing: 0) {
|
||||
|
||||
@@ -128,17 +128,17 @@ public struct ProfileView: View {
|
||||
if viewModel.editMode == .inactive {
|
||||
Button(action: {
|
||||
viewModel.editMode = .active
|
||||
}) {
|
||||
}, label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
}
|
||||
})
|
||||
.tint(.accentColor)
|
||||
.disabled(viewModel.profileList.isEmpty)
|
||||
} else {
|
||||
Button(action: {
|
||||
viewModel.editMode = .inactive
|
||||
}) {
|
||||
}, label: {
|
||||
Image(systemName: "checkmark.square.fill")
|
||||
}
|
||||
})
|
||||
.tint(.accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,20 +57,12 @@ public struct QRCodeSheet: View {
|
||||
if #available(iOS 16.0, tvOS 17.0, *) {
|
||||
NavigationStackCompat {
|
||||
QRCodeContentView(profileName: profileName, remoteURL: remoteURL)
|
||||
.navigationTitle("Share QR Code")
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([.medium])
|
||||
.presentationDragIndicator(.visible)
|
||||
} else {
|
||||
NavigationStackCompat {
|
||||
QRCodeContentView(profileName: profileName, remoteURL: remoteURL)
|
||||
.navigationTitle("Share QR Code")
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import Libbox
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
|
||||
+68
-9
@@ -197,7 +197,14 @@
|
||||
}
|
||||
},
|
||||
"Cancel" : {
|
||||
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "取消"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Chain" : {
|
||||
"localizations" : {
|
||||
@@ -330,6 +337,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Connections (%lld)" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "连接 (%lld)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Copy" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -512,7 +529,14 @@
|
||||
}
|
||||
},
|
||||
"Do you want to save the changes you made?" : {
|
||||
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "您要保存所做的更改吗?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Documentation" : {
|
||||
"localizations" : {
|
||||
@@ -535,7 +559,14 @@
|
||||
}
|
||||
},
|
||||
"Don't Save" : {
|
||||
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "不保存"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Done" : {
|
||||
"localizations" : {
|
||||
@@ -752,6 +783,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Groups (%lld)" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "组 (%lld)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Handled unknown URL %@" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -1028,7 +1069,14 @@
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"Manage profiles" : {
|
||||
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "管理配置"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Match Rule" : {
|
||||
"shouldTranslate" : false
|
||||
@@ -1500,9 +1548,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Share QR Code" : {
|
||||
|
||||
},
|
||||
"Share URL as QR Code" : {
|
||||
"localizations" : {
|
||||
@@ -1761,7 +1806,14 @@
|
||||
}
|
||||
},
|
||||
"Unsaved Changes" : {
|
||||
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "未保存的更改"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Update" : {
|
||||
"localizations" : {
|
||||
@@ -1774,7 +1826,14 @@
|
||||
}
|
||||
},
|
||||
"Update Failed" : {
|
||||
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "更新失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Uplink" : {
|
||||
"localizations" : {
|
||||
|
||||
@@ -63,8 +63,6 @@ open class ApplicationDelegate: NSObject, NSApplicationDelegate, UNUserNotificat
|
||||
switch response.actionIdentifier {
|
||||
case "COPY_URL":
|
||||
NSPasteboard.general.setString(url, forType: .URL)
|
||||
case "OPEN_URL":
|
||||
fallthrough
|
||||
default:
|
||||
NSWorkspace.shared.open(URL(string: url)!)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public struct MainView: View {
|
||||
Label("Dashboard Items", systemImage: "square.grid.2x2")
|
||||
}
|
||||
} label: {
|
||||
Label("Others", systemImage: "ellipsis.circle")
|
||||
Label("Others", systemImage: "line.3.horizontal.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,9 +65,11 @@ public struct MainView: View {
|
||||
.environment(\.profileEditor, profileEditor)
|
||||
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
||||
.onOpenURL(perform: viewModel.openURL)
|
||||
.sheet(isPresented: $showCardManagement) {
|
||||
CardManagementSheet(configurationVersion: $cardConfigurationVersion)
|
||||
.sheet(isPresented: $showCardManagement, onDismiss: {
|
||||
cardConfigurationVersion += 1
|
||||
}, content: {
|
||||
CardManagementSheet()
|
||||
.frame(minWidth: 400, minHeight: 400)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
all: ios macos macos_standalone tvos
|
||||
|
||||
ios:
|
||||
xcodebuild build -scheme SFI -configuration Debug -destination 'generic/platform=iOS' | xcbeautify | grep -A 3 -e "Build Succeeded" -e "BUILD FAILED" -e "❌"
|
||||
|
||||
macos:
|
||||
xcodebuild build -scheme SFM -configuration Debug -destination 'generic/platform=macOS' | xcbeautify | grep -A 3 -e "Build Succeeded" -e "BUILD FAILED" -e "❌"
|
||||
|
||||
macos_standalone:
|
||||
xcodebuild build -scheme SFM.System -configuration Debug -destination 'generic/platform=macOS' | xcbeautify | grep -A 3 -e "Build Succeeded" -e "BUILD FAILED" -e "❌"
|
||||
|
||||
tvos:
|
||||
xcodebuild build -scheme SFT -configuration Debug -destination 'generic/platform=tvOS' | xcbeautify | grep -A 3 -e "Build Succeeded" -e "BUILD FAILED" -e "❌"
|
||||
|
||||
fmt:
|
||||
swiftformat .
|
||||
|
||||
fmt_install:
|
||||
brew install swiftformat
|
||||
|
||||
lint:
|
||||
swiftlint
|
||||
|
||||
lint_install:
|
||||
brew install swiftlint
|
||||
@@ -44,8 +44,6 @@ class ApplicationDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCe
|
||||
switch response.actionIdentifier {
|
||||
case "COPY_URL":
|
||||
UIPasteboard.general.string = url
|
||||
case "OPEN_URL":
|
||||
fallthrough
|
||||
default:
|
||||
await UIApplication.shared.open(URL(string: url)!)
|
||||
}
|
||||
|
||||
+3
-1
@@ -51,8 +51,8 @@ struct MainView: View {
|
||||
HStack(spacing: 12) {
|
||||
if let profile = environments.extensionProfile {
|
||||
StatusText(profile: profile)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
Spacer()
|
||||
NavigationButtonsView(
|
||||
showGroupsButton: buttonState.showGroupsButton,
|
||||
showConnectionsButton: buttonState.showConnectionsButton,
|
||||
@@ -187,6 +187,8 @@ struct MainView: View {
|
||||
Text(statusText)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
.fixedSize()
|
||||
}
|
||||
|
||||
private var statusText: String {
|
||||
|
||||
Reference in New Issue
Block a user