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