321 lines
11 KiB
Swift
321 lines
11 KiB
Swift
import Foundation
|
|
import Libbox
|
|
import Library
|
|
import SwiftUI
|
|
|
|
@MainActor public struct ActiveDashboardView: View {
|
|
@Environment(\.scenePhase) private var scenePhase
|
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
|
@EnvironmentObject private var profile: ExtensionProfile
|
|
@ObservedObject private var coordinator: DashboardViewModel
|
|
@StateObject private var cardConfiguration = DashboardCardConfiguration()
|
|
#if os(iOS) || os(tvOS)
|
|
@State private var showCardManagement = false
|
|
@State private var showGroups = false
|
|
@State private var showConnections = false
|
|
@State private var buttonState = ButtonVisibilityState()
|
|
#endif
|
|
#if os(macOS)
|
|
@Environment(\.cardConfigurationVersion) private var cardConfigurationVersion
|
|
#endif
|
|
|
|
public init(coordinator: DashboardViewModel) {
|
|
_coordinator = ObservedObject(wrappedValue: coordinator)
|
|
}
|
|
|
|
public var body: some View {
|
|
viewContent
|
|
}
|
|
|
|
@ViewBuilder private var viewContent: some View {
|
|
if coordinator.isLoading {
|
|
ProgressView()
|
|
#if os(iOS) || os(tvOS)
|
|
.onAppear {
|
|
Task {
|
|
await coordinator.reload()
|
|
}
|
|
}
|
|
#endif
|
|
} else {
|
|
content.onAppear {
|
|
guard !Variant.screenshotMode, profile.status.isConnected else {
|
|
return
|
|
}
|
|
Task {
|
|
await coordinator.reloadSystemProxy()
|
|
}
|
|
}.onChangeCompat(of: profile.status) { status in
|
|
guard !Variant.screenshotMode, status == .connected else {
|
|
return
|
|
}
|
|
Task {
|
|
await coordinator.reloadSystemProxy()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@ViewBuilder private var content: some View {
|
|
Group {
|
|
#if os(iOS)
|
|
if useLegacyTabView {
|
|
if Variant.screenshotMode || profile.status.isConnectedStrict {
|
|
VStack {
|
|
pageSelector
|
|
pageContent
|
|
}
|
|
} else {
|
|
overviewPage
|
|
}
|
|
} else {
|
|
overviewPage
|
|
}
|
|
#else
|
|
overviewPage
|
|
#endif
|
|
}
|
|
#if os(iOS) || os(tvOS)
|
|
.toolbar {
|
|
toolbar
|
|
}
|
|
#if os(tvOS)
|
|
.navigationDestination(isPresented: $showGroups) {
|
|
GroupListView()
|
|
.navigationTitle("Groups")
|
|
.toolbar {
|
|
ToolbarItemGroup(placement: .topBarLeading) {
|
|
BackButton()
|
|
}
|
|
}
|
|
}
|
|
.navigationDestination(isPresented: $showConnections) {
|
|
ConnectionListView()
|
|
.navigationTitle("Connections")
|
|
.toolbar {
|
|
ToolbarItemGroup(placement: .topBarLeading) {
|
|
BackButton()
|
|
}
|
|
}
|
|
}
|
|
.navigationDestination(isPresented: $showCardManagement) {
|
|
CardManagementView(onDisappear: {
|
|
Task { await cardConfiguration.reload() }
|
|
})
|
|
.navigationTitle("Dashboard Items")
|
|
.toolbar {
|
|
ToolbarItemGroup(placement: .topBarLeading) {
|
|
BackButton()
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
.sheet(isPresented: $showGroups) {
|
|
groupsSheetContent
|
|
}.sheet(isPresented: $showConnections) {
|
|
connectionsSheetContent
|
|
}.sheet(isPresented: $showCardManagement, onDismiss: {
|
|
Task { await cardConfiguration.reload() }
|
|
}, content: {
|
|
if #available(iOS 16.0, *) {
|
|
CardManagementSheet().presentationDetents([.large]).presentationDragIndicator(.visible)
|
|
} else {
|
|
CardManagementSheet()
|
|
}
|
|
})
|
|
#endif
|
|
#endif
|
|
.onAppear {
|
|
environments.connect()
|
|
}.onChangeCompat(of: scenePhase) { phase in
|
|
guard phase == .active else {
|
|
return
|
|
}
|
|
environments.connect()
|
|
}.onChangeCompat(of: profile.status) { status in
|
|
guard status.isConnected else {
|
|
return
|
|
}
|
|
environments.connect()
|
|
}.onReceive(environments.profileUpdate) { _ in
|
|
Task {
|
|
await coordinator.reload()
|
|
}
|
|
}.onReceive(environments.selectedProfileUpdate) { _ in
|
|
Task {
|
|
await coordinator.updateSelectedProfile()
|
|
if profile.status.isConnected {
|
|
await coordinator.reloadSystemProxy()
|
|
}
|
|
}
|
|
}
|
|
#if os(iOS) || os(tvOS)
|
|
.onReceive(environments.commandClient.$groups) { _ in
|
|
Task { @MainActor in
|
|
updateButtonVisibility()
|
|
#if os(iOS)
|
|
if useLegacyTabView, coordinator.selection == .groups, !buttonState.showGroupsButton {
|
|
coordinator.selection = .overview
|
|
}
|
|
#endif
|
|
}
|
|
}.onReceive(profile.$status) { _ in
|
|
Task { @MainActor in
|
|
updateButtonVisibility()
|
|
#if os(iOS)
|
|
if useLegacyTabView, coordinator.selection == .groups, !buttonState.showGroupsButton {
|
|
coordinator.selection = .overview
|
|
}
|
|
#endif
|
|
}
|
|
}.onAppear {
|
|
updateButtonVisibility()
|
|
}
|
|
#endif
|
|
#if os(macOS)
|
|
.onChangeCompat(of: cardConfigurationVersion) { _ in
|
|
Task { await cardConfiguration.reload() }
|
|
}
|
|
#endif
|
|
}
|
|
|
|
@ViewBuilder private var overviewPage: some View {
|
|
OverviewView(
|
|
$coordinator.profileList,
|
|
$coordinator.selectedProfileID,
|
|
$coordinator.systemProxyAvailable,
|
|
$coordinator.systemProxyEnabled,
|
|
cardConfiguration: cardConfiguration
|
|
)
|
|
}
|
|
|
|
#if os(iOS) || os(tvOS)
|
|
private func updateButtonVisibility() {
|
|
buttonState.update(profile: profile, commandClient: environments.commandClient)
|
|
}
|
|
|
|
#if os(iOS)
|
|
private var isTabViewBottomAccessoryAvailable: Bool {
|
|
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
private var useLegacyTabView: Bool {
|
|
!isTabViewBottomAccessoryAvailable
|
|
}
|
|
|
|
private var enabledPages: [DashboardPage] {
|
|
DashboardPage.enabledCases(hasGroups: buttonState.showGroupsButton)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var pageSelector: some View {
|
|
Picker("Page", selection: $coordinator.selection) {
|
|
ForEach(enabledPages) { page in
|
|
page.label
|
|
}
|
|
}
|
|
.pickerStyle(.segmented)
|
|
.padding([.leading, .trailing])
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var pageContent: some View {
|
|
TabView(selection: $coordinator.selection) {
|
|
ForEach(enabledPages) { page in
|
|
page.contentView(
|
|
$coordinator.profileList,
|
|
$coordinator.selectedProfileID,
|
|
$coordinator.systemProxyAvailable,
|
|
$coordinator.systemProxyEnabled,
|
|
cardConfiguration
|
|
)
|
|
.tag(page)
|
|
}
|
|
}
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.tabViewStyle(.page(indexDisplayMode: .never))
|
|
}
|
|
#endif
|
|
|
|
@ToolbarContentBuilder private var toolbar: some ToolbarContent {
|
|
#if os(tvOS)
|
|
ToolbarItemGroup(placement: .topBarLeading) {
|
|
navigationButtons
|
|
}
|
|
#endif
|
|
ToolbarItemGroup(placement: .topBarTrailing) {
|
|
if #available(iOS 16.0, tvOS 17.0, *) {
|
|
#if os(iOS)
|
|
if !useLegacyTabView || coordinator.selection == .overview {
|
|
cardManagementButton
|
|
}
|
|
#else
|
|
cardManagementButton
|
|
#endif
|
|
}
|
|
#if os(tvOS)
|
|
StartStopButton()
|
|
#elseif os(iOS)
|
|
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
|
} else if !useLegacyTabView || coordinator.selection != .connections {
|
|
StartStopButton()
|
|
}
|
|
#else
|
|
StartStopButton()
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if os(tvOS)
|
|
private var navigationButtons: some View {
|
|
NavigationButtonsView(
|
|
showGroupsButton: buttonState.showGroupsButton,
|
|
showConnectionsButton: buttonState.showConnectionsButton,
|
|
groupsCount: buttonState.groupsCount,
|
|
connectionsCount: buttonState.connectionsCount,
|
|
onGroupsTap: {
|
|
showGroups = true
|
|
},
|
|
onConnectionsTap: {
|
|
showConnections = true
|
|
}
|
|
)
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if os(iOS) || os(tvOS)
|
|
private var groupsSheetContent: some View {
|
|
GroupsSheetContent()
|
|
}
|
|
|
|
private var connectionsSheetContent: some View {
|
|
ConnectionsSheetContent()
|
|
}
|
|
|
|
@available(iOS 16.0, *) @ViewBuilder private var cardManagementButton: some View {
|
|
#if os(iOS)
|
|
Menu {
|
|
Button {
|
|
showCardManagement = true
|
|
} label: {
|
|
Label("Dashboard Items", systemImage: "square.grid.2x2")
|
|
}
|
|
} label: {
|
|
Label("Others", systemImage: "line.3.horizontal.circle")
|
|
}
|
|
#elseif os(tvOS)
|
|
Button {
|
|
showCardManagement = true
|
|
} label: {
|
|
Image(systemName: "square.grid.2x2")
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|