Refactor ViewModel
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
open class BaseViewModel: ObservableObject {
|
||||
@Published public var alert: Alert?
|
||||
@Published public var isLoading = false
|
||||
|
||||
public init() {}
|
||||
|
||||
public func showError(_ error: Error) {
|
||||
alert = Alert(error)
|
||||
}
|
||||
|
||||
public func execute(_ operation: () async throws -> Void) async {
|
||||
do {
|
||||
try await operation()
|
||||
} catch {
|
||||
alert = Alert(error)
|
||||
}
|
||||
}
|
||||
|
||||
public func executeOnBackground(_ operation: @escaping @Sendable () async throws -> Void) async {
|
||||
do {
|
||||
try await operation()
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
alert = Alert(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public struct NavigationButtonsView: View {
|
||||
|
||||
#if os(tvOS)
|
||||
private var tvOSBody: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if showConnectionsButton {
|
||||
Button {
|
||||
onConnectionsTap()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import SwiftUI
|
||||
|
||||
public func NavigationStackCompat(@ViewBuilder content: () -> some View) -> some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if #available(iOS 17.0, macOS 13.0, tvOS 17.0, *) {
|
||||
// view not updating in iOS 16, but why?
|
||||
NavigationStack {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import SwiftUI
|
||||
|
||||
public func RequestReviewButton(label: @escaping () -> some View) -> some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if #available(iOS 16.0, macOS 13.0, visionOS 1.0, *) {
|
||||
RequestReviewButton0(label: label)
|
||||
} else {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public func viewBuilder(@ViewBuilder _ builder: () -> some View) -> some View {
|
||||
builder()
|
||||
}
|
||||
@@ -2,22 +2,12 @@ import SwiftUI
|
||||
|
||||
public extension View {
|
||||
func onChangeCompat(of value: some Equatable, _ action: @escaping () -> Void) -> some View {
|
||||
// if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) {
|
||||
// return onChange(of: value, action)
|
||||
// } else {
|
||||
onChange(of: value) { _ in
|
||||
action()
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
func onChangeCompat<V>(of value: V, _ action: @escaping (_ newValue: V) -> Void) -> some View where V: Equatable {
|
||||
// if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) {
|
||||
// return onChange(of: value) { _, newValue in
|
||||
// action(newValue)
|
||||
// }
|
||||
// } else {
|
||||
onChange(of: value, perform: action)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@ import Library
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public class ConnectionListViewModel: ObservableObject {
|
||||
@Published public var isLoading = true
|
||||
public class ConnectionListViewModel: BaseViewModel {
|
||||
@Published public var connections: [Connection] = []
|
||||
@Published public var searchText = ""
|
||||
@Published public var alert: Alert?
|
||||
@Published public var connectionStateFilter: ConnectionStateFilter {
|
||||
didSet {
|
||||
saveStateFilterTask?.cancel()
|
||||
@@ -30,9 +28,11 @@ public class ConnectionListViewModel: ObservableObject {
|
||||
private var saveStateFilterTask: Task<Void, Never>?
|
||||
private var saveSortTask: Task<Void, Never>?
|
||||
|
||||
public init() {
|
||||
public override init() {
|
||||
connectionStateFilter = .active
|
||||
connectionSort = .byDate
|
||||
super.init()
|
||||
isLoading = true
|
||||
}
|
||||
|
||||
public func connect() {
|
||||
|
||||
@@ -7,7 +7,7 @@ import SwiftUI
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||
@EnvironmentObject private var profile: ExtensionProfile
|
||||
@StateObject private var coordinator = DashboardCoordinator()
|
||||
@StateObject private var coordinator = DashboardViewModel()
|
||||
@State private var cardConfigurationVersion = 0
|
||||
#if os(iOS) || os(tvOS)
|
||||
@State private var showCardManagement = false
|
||||
|
||||
@@ -25,7 +25,7 @@ public struct ExtensionStatusView: View {
|
||||
}
|
||||
|
||||
public var body0: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
VStack {
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) {
|
||||
if ApplicationLibrary.inPreview {
|
||||
|
||||
@@ -9,7 +9,7 @@ public struct StartStopButton: View {
|
||||
public init() {}
|
||||
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if ApplicationLibrary.inPreview {
|
||||
Button {} label: {
|
||||
#if os(tvOS)
|
||||
|
||||
@@ -52,7 +52,7 @@ public extension DashboardPage {
|
||||
|
||||
@MainActor
|
||||
func contentView(_ profileList: Binding<[ProfilePreview]>, _ selectedProfileID: Binding<Int64>, _ systemProxyAvailable: Binding<Bool>, _ systemProxyEnabled: Binding<Bool>, _ cardConfigurationVersion: Int) -> some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
switch self {
|
||||
case .overview:
|
||||
OverviewView(profileList, selectedProfileID, systemProxyAvailable, systemProxyEnabled, cardConfigurationVersion: cardConfigurationVersion)
|
||||
|
||||
@@ -9,7 +9,7 @@ public struct DashboardView: View {
|
||||
@Environment(\.importProfile) private var importProfile
|
||||
@Environment(\.importRemoteProfile) private var importRemoteProfile
|
||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||
@StateObject private var coordinator = DashboardCoordinator()
|
||||
@StateObject private var coordinator = DashboardViewModel()
|
||||
@State private var importRemoteProfileRequest: NewProfileView.ImportRequest?
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
+5
-4
@@ -5,11 +5,9 @@ import NetworkExtension
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public final class DashboardCoordinator: ObservableObject {
|
||||
@Published public var isLoading = true
|
||||
public final class DashboardViewModel: BaseViewModel {
|
||||
@Published public var profileList: [ProfilePreview] = []
|
||||
@Published public var selectedProfileID: Int64 = 0
|
||||
@Published public var alert: Alert?
|
||||
@Published public var selection = DashboardPage.overview
|
||||
@Published public var systemProxyAvailable = false
|
||||
@Published public var systemProxyEnabled = false
|
||||
@@ -22,7 +20,10 @@ public final class DashboardCoordinator: ObservableObject {
|
||||
public var onEmptyProfilesChange: ((Bool) -> Void)?
|
||||
private var openURL: ((URL) -> Void)?
|
||||
|
||||
public init() {}
|
||||
public override init() {
|
||||
super.init()
|
||||
isLoading = true
|
||||
}
|
||||
|
||||
public func setOpenURL(_ openURL: @escaping (URL) -> Void) {
|
||||
self.openURL = openURL
|
||||
@@ -7,7 +7,7 @@ import SwiftUI
|
||||
public struct OverviewView: View {
|
||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||
@EnvironmentObject private var profile: ExtensionProfile
|
||||
@StateObject private var coordinator = OverviewCoordinator()
|
||||
@StateObject private var coordinator = OverviewViewModel()
|
||||
@StateObject private var configuration = DashboardCardConfiguration()
|
||||
|
||||
@Binding private var profileList: [ProfilePreview]
|
||||
|
||||
+1
-4
@@ -4,12 +4,9 @@ import Library
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public final class OverviewCoordinator: ObservableObject {
|
||||
@Published public var alert: Alert?
|
||||
public final class OverviewViewModel: BaseViewModel {
|
||||
@Published public var reasserting = false
|
||||
|
||||
public init() {}
|
||||
|
||||
public func switchProfile(_ profileID: Int64, profile: ExtensionProfile, environments: ExtensionEnvironments) async {
|
||||
await SharedPreferences.selectedProfileID.set(profileID)
|
||||
environments.selectedProfileUpdate.send()
|
||||
@@ -3,14 +3,15 @@ import Library
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public class GroupListViewModel: ObservableObject {
|
||||
@Published public var isLoading = true
|
||||
public class GroupListViewModel: BaseViewModel {
|
||||
@Published public var groups: [OutboundGroup] = []
|
||||
@Published public var alert: Alert?
|
||||
|
||||
private var pendingSelections: [String: String] = [:]
|
||||
|
||||
public init() {}
|
||||
public override init() {
|
||||
super.init()
|
||||
isLoading = true
|
||||
}
|
||||
|
||||
public func connect() {
|
||||
if ApplicationLibrary.inPreview {
|
||||
|
||||
@@ -64,7 +64,7 @@ public extension NavigationPage {
|
||||
|
||||
@MainActor
|
||||
var contentView: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
switch self {
|
||||
case .dashboard:
|
||||
DashboardView()
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
@Environment(\.profileEditor) private var profileEditor
|
||||
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if viewModel.isLoading {
|
||||
ProgressView().onAppear {
|
||||
Task {
|
||||
@@ -104,7 +104,7 @@
|
||||
|
||||
@ViewBuilder
|
||||
private var defaultEditorView: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if readOnly {
|
||||
TextEditor(text: .constant(viewModel.profileContent))
|
||||
} else {
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public final class EditProfileContentViewModel: ObservableObject {
|
||||
@Published public var isLoading = true
|
||||
public final class EditProfileContentViewModel: BaseViewModel {
|
||||
@Published public var profile: Profile?
|
||||
@Published public var profileContent = ""
|
||||
@Published public var isChanged = false
|
||||
@Published public var alert: Alert?
|
||||
|
||||
private let profileID: Int64?
|
||||
|
||||
public init(profileID: Int64?) {
|
||||
self.profileID = profileID
|
||||
super.init()
|
||||
isLoading = true
|
||||
}
|
||||
|
||||
public func markAsChanged() {
|
||||
|
||||
@@ -3,15 +3,11 @@ import Library
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public final class EditProfileViewModel: ObservableObject {
|
||||
@Published public var isLoading = false
|
||||
public final class EditProfileViewModel: BaseViewModel {
|
||||
@Published public var isChanged = false
|
||||
@Published public var alert: Alert?
|
||||
@Published public var shareLinkPresented = false
|
||||
@Published public var shareLinkText: String?
|
||||
|
||||
public init() {}
|
||||
|
||||
public func markAsChanged() {
|
||||
isChanged = true
|
||||
}
|
||||
|
||||
@@ -7,17 +7,13 @@
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public final class ImportProfileViewModel: ObservableObject {
|
||||
@Published public var isLoading = false
|
||||
public final class ImportProfileViewModel: BaseViewModel {
|
||||
@Published public var selected = false
|
||||
@Published public var alert: Alert?
|
||||
@Published public var connection: NWConnection?
|
||||
@Published public var socket: NWSocket?
|
||||
@Published public var profiles: [LibboxProfilePreview]?
|
||||
@Published public var isImporting = false
|
||||
|
||||
public init() {}
|
||||
|
||||
public func reset() {
|
||||
if let connection {
|
||||
connection.stateUpdateHandler = nil
|
||||
|
||||
@@ -57,7 +57,7 @@ public struct NewProfileView: View {
|
||||
#if os(tvOS)
|
||||
.disabled(true)
|
||||
#endif
|
||||
viewBuilder {
|
||||
Group {
|
||||
if viewModel.fileImport {
|
||||
HStack {
|
||||
Text("File Path")
|
||||
|
||||
@@ -4,7 +4,7 @@ import Library
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public final class NewProfileViewModel: ObservableObject {
|
||||
public final class NewProfileViewModel: BaseViewModel {
|
||||
@Published public var isSaving = false
|
||||
@Published public var profileName = ""
|
||||
#if !os(tvOS)
|
||||
@@ -18,9 +18,9 @@ public final class NewProfileViewModel: ObservableObject {
|
||||
@Published public var autoUpdate = true
|
||||
@Published public var autoUpdateInterval: Int32 = 60
|
||||
@Published public var pickerPresented = false
|
||||
@Published public var alert: Alert?
|
||||
|
||||
public init(importRequest: NewProfileView.ImportRequest? = nil) {
|
||||
super.init()
|
||||
if let importRequest {
|
||||
profileName = importRequest.name
|
||||
profileType = .remote
|
||||
|
||||
@@ -67,7 +67,7 @@ public struct ProfileView: View {
|
||||
} else {
|
||||
List {
|
||||
ForEach(viewModel.profileList, id: \.id) { profile in
|
||||
viewBuilder {
|
||||
Group {
|
||||
#if os(iOS) || os(tvOS)
|
||||
if viewModel.editMode.isEditing == true {
|
||||
Text(profile.name)
|
||||
@@ -182,7 +182,7 @@ public struct ProfileView: View {
|
||||
}
|
||||
|
||||
private var draggableBody: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
#if !os(macOS)
|
||||
FormNavigationLink {
|
||||
EditProfileView().environmentObject(profile.origin)
|
||||
|
||||
@@ -4,12 +4,10 @@ import Library
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public class ProfileViewModel: ObservableObject {
|
||||
public class ProfileViewModel: BaseViewModel {
|
||||
@Published public var importRemoteProfileRequest: NewProfileView.ImportRequest?
|
||||
@Published public var importRemoteProfilePresented = false
|
||||
@Published public var isLoading = true
|
||||
@Published public var isUpdating = false
|
||||
@Published public var alert: Alert?
|
||||
@Published public var profileList: [ProfilePreview] = []
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
@@ -18,7 +16,10 @@ public class ProfileViewModel: ObservableObject {
|
||||
|
||||
private weak var environments: ExtensionEnvironments?
|
||||
|
||||
public init() {}
|
||||
public override init() {
|
||||
super.init()
|
||||
isLoading = true
|
||||
}
|
||||
|
||||
public func setEnvironments(_ environments: ExtensionEnvironments) {
|
||||
self.environments = environments
|
||||
|
||||
@@ -13,7 +13,7 @@ public struct CoreView: View {
|
||||
|
||||
public init() {}
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if isLoading {
|
||||
ProgressView().onAppear {
|
||||
Task {
|
||||
|
||||
@@ -17,7 +17,7 @@ public struct AppView: View {
|
||||
|
||||
public init() {}
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if isLoading {
|
||||
ProgressView().onAppear {
|
||||
Task {
|
||||
|
||||
@@ -8,7 +8,7 @@ public struct OnDemandRulesView: View {
|
||||
|
||||
public init() {}
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if isLoading {
|
||||
ProgressView().onAppear {
|
||||
Task.detached {
|
||||
|
||||
@@ -14,7 +14,7 @@ struct PacketTunnelView: View {
|
||||
|
||||
init() {}
|
||||
var body: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if isLoading {
|
||||
ProgressView().onAppear {
|
||||
Task.detached {
|
||||
|
||||
@@ -9,7 +9,7 @@ public struct ProfileOverrideView: View {
|
||||
|
||||
public init() {}
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if isLoading {
|
||||
ProgressView().onAppear {
|
||||
Task.detached {
|
||||
|
||||
@@ -12,7 +12,7 @@ public struct ServiceLogView: View {
|
||||
public init() {}
|
||||
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if viewModel.isLoading {
|
||||
ProgressView().onAppear {
|
||||
Task {
|
||||
|
||||
@@ -57,7 +57,7 @@ public struct SettingView: View {
|
||||
|
||||
@MainActor
|
||||
var contentView: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
switch self {
|
||||
#if os(macOS)
|
||||
case .app:
|
||||
|
||||
@@ -129,7 +129,7 @@ public struct MenuView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if isLoading {
|
||||
ProgressView().onAppear {
|
||||
Task {
|
||||
|
||||
+55
-86
@@ -37,46 +37,28 @@ struct MainView: View {
|
||||
@ViewBuilder
|
||||
private var tabViewContent: some View {
|
||||
if shouldShowBottomAccessory {
|
||||
TabView(selection: $selection) {
|
||||
ForEach(NavigationPage.allCases, id: \.self) { page in
|
||||
NavigationStackCompat {
|
||||
page.contentView
|
||||
.navigationTitle(page.title)
|
||||
baseTabView
|
||||
.tabViewBottomAccessory {
|
||||
HStack(spacing: 12) {
|
||||
if let profile = environments.extensionProfile {
|
||||
StatusText(profile: profile)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
NavigationButtonsView(
|
||||
showGroupsButton: buttonState.showGroupsButton,
|
||||
showConnectionsButton: buttonState.showConnectionsButton,
|
||||
groupsCount: buttonState.groupsCount,
|
||||
connectionsCount: buttonState.connectionsCount,
|
||||
onGroupsTap: { showGroups = true },
|
||||
onConnectionsTap: { showConnections = true }
|
||||
)
|
||||
Divider()
|
||||
StartStopButton()
|
||||
}
|
||||
.tag(page)
|
||||
.tabItem { page.label }
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
.tabViewBottomAccessory {
|
||||
HStack(spacing: 12) {
|
||||
if let profile = environments.extensionProfile {
|
||||
StatusText(profile: profile)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
NavigationButtonsView(
|
||||
showGroupsButton: buttonState.showGroupsButton,
|
||||
showConnectionsButton: buttonState.showConnectionsButton,
|
||||
groupsCount: buttonState.groupsCount,
|
||||
connectionsCount: buttonState.connectionsCount,
|
||||
onGroupsTap: { showGroups = true },
|
||||
onConnectionsTap: { showConnections = true }
|
||||
)
|
||||
Divider()
|
||||
StartStopButton()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
} else {
|
||||
TabView(selection: $selection) {
|
||||
ForEach(NavigationPage.allCases, id: \.self) { page in
|
||||
NavigationStackCompat {
|
||||
page.contentView
|
||||
.navigationTitle(page.title)
|
||||
}
|
||||
.tag(page)
|
||||
.tabItem { page.label }
|
||||
}
|
||||
}
|
||||
baseTabView
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,25 +70,27 @@ struct MainView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var baseTabView: some View {
|
||||
TabView(selection: $selection) {
|
||||
ForEach(NavigationPage.allCases, id: \.self) { page in
|
||||
NavigationStackCompat {
|
||||
page.contentView
|
||||
.navigationTitle(page.title)
|
||||
}
|
||||
.tag(page)
|
||||
.tabItem { page.label }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var mainBody: some View {
|
||||
viewBuilder {
|
||||
Group {
|
||||
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
||||
tabViewContent
|
||||
.onAppear {
|
||||
environments.postReload()
|
||||
updateButtonVisibility()
|
||||
}
|
||||
.alertBinding($alert)
|
||||
.onChangeCompat(of: scenePhase) { newValue in
|
||||
if newValue == .active {
|
||||
environments.postReload()
|
||||
}
|
||||
}
|
||||
.onChangeCompat(of: selection) { newValue in
|
||||
if newValue == .logs {
|
||||
environments.connect()
|
||||
}
|
||||
}
|
||||
.onReceive(environments.commandClient.$groups) { _ in
|
||||
updateButtonVisibility()
|
||||
}
|
||||
@@ -125,12 +109,6 @@ struct MainView: View {
|
||||
.onReceive(environments.$emptyProfiles) { _ in
|
||||
updateButtonVisibility()
|
||||
}
|
||||
.environment(\.selection, $selection)
|
||||
.environment(\.importProfile, $importProfile)
|
||||
.environment(\.importRemoteProfile, $importRemoteProfile)
|
||||
.environment(\.profileEditor, profileEditor)
|
||||
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
||||
.onOpenURL(perform: openURL)
|
||||
.sheet(isPresented: $showGroups) {
|
||||
GroupsSheetContent()
|
||||
}
|
||||
@@ -138,38 +116,29 @@ struct MainView: View {
|
||||
ConnectionsSheetContent()
|
||||
}
|
||||
} else {
|
||||
TabView(selection: $selection) {
|
||||
ForEach(NavigationPage.allCases, id: \.self) { page in
|
||||
NavigationStackCompat {
|
||||
page.contentView
|
||||
.navigationTitle(page.title)
|
||||
}
|
||||
.tag(page)
|
||||
.tabItem { page.label }
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
environments.postReload()
|
||||
}
|
||||
.alertBinding($alert)
|
||||
.onChangeCompat(of: scenePhase) { newValue in
|
||||
if newValue == .active {
|
||||
environments.postReload()
|
||||
}
|
||||
}
|
||||
.onChangeCompat(of: selection) { newValue in
|
||||
if newValue == .logs {
|
||||
environments.connect()
|
||||
}
|
||||
}
|
||||
.environment(\.selection, $selection)
|
||||
.environment(\.importProfile, $importProfile)
|
||||
.environment(\.importRemoteProfile, $importRemoteProfile)
|
||||
.environment(\.profileEditor, profileEditor)
|
||||
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
||||
.onOpenURL(perform: openURL)
|
||||
baseTabView
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
environments.postReload()
|
||||
}
|
||||
.alertBinding($alert)
|
||||
.onChangeCompat(of: scenePhase) { newValue in
|
||||
if newValue == .active {
|
||||
environments.postReload()
|
||||
}
|
||||
}
|
||||
.onChangeCompat(of: selection) { newValue in
|
||||
if newValue == .logs {
|
||||
environments.connect()
|
||||
}
|
||||
}
|
||||
.environment(\.selection, $selection)
|
||||
.environment(\.importProfile, $importProfile)
|
||||
.environment(\.importRemoteProfile, $importRemoteProfile)
|
||||
.environment(\.profileEditor, profileEditor)
|
||||
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
||||
.onOpenURL(perform: openURL)
|
||||
}
|
||||
|
||||
private func updateButtonVisibility() {
|
||||
|
||||
Reference in New Issue
Block a user