refactor: Tab view accessory compat
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
public extension View {
|
||||||
|
@ViewBuilder
|
||||||
|
func tabViewBottomAccessoryCompat<Content: View>(
|
||||||
|
isEnabled: Bool = true,
|
||||||
|
useSystemAccessory: Bool = true,
|
||||||
|
@ViewBuilder content: @escaping () -> Content
|
||||||
|
) -> some View {
|
||||||
|
if isEnabled {
|
||||||
|
if #available(iOS 26.0, *), useSystemAccessory {
|
||||||
|
tabViewBottomAccessory {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
safeAreaInset(edge: .bottom, spacing: 0) {
|
||||||
|
TabViewBottomAccessoryContainer(content: content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TabViewBottomAccessoryContainer<Content: View>: View {
|
||||||
|
@ViewBuilder let content: () -> Content
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
content()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: TabViewBottomAccessoryMetrics.height)
|
||||||
|
.background(
|
||||||
|
.bar,
|
||||||
|
in: RoundedRectangle(
|
||||||
|
cornerRadius: TabViewBottomAccessoryMetrics.cornerRadius,
|
||||||
|
style: .continuous
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.padding(.horizontal, TabViewBottomAccessoryMetrics.horizontalPadding)
|
||||||
|
.padding(.top, TabViewBottomAccessoryMetrics.topPadding)
|
||||||
|
.padding(.bottom, TabViewBottomAccessoryMetrics.bottomPadding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TabViewBottomAccessoryMetrics {
|
||||||
|
static var height: CGFloat {
|
||||||
|
let toolbar = UIToolbar()
|
||||||
|
let size = toolbar.sizeThatFits(CGSize(width: UIScreen.main.bounds.width, height: 0))
|
||||||
|
return size.height > 0 ? size.height : 44
|
||||||
|
}
|
||||||
|
|
||||||
|
static var cornerRadius: CGFloat {
|
||||||
|
height * 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
static let horizontalPadding: CGFloat = 20
|
||||||
|
static let topPadding: CGFloat = 8
|
||||||
|
static let bottomPadding: CGFloat = 12
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -37,10 +37,6 @@ public struct ConnectionListView: View {
|
|||||||
connectionSort: $viewModel.connectionSort,
|
connectionSort: $viewModel.connectionSort,
|
||||||
closeAllConnections: viewModel.closeAllConnections
|
closeAllConnections: viewModel.closeAllConnections
|
||||||
)
|
)
|
||||||
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
|
||||||
} else {
|
|
||||||
StartStopButton()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
@@ -82,16 +78,6 @@ public struct ConnectionListView: View {
|
|||||||
.background(Color(uiColor: .systemGroupedBackground))
|
.background(Color(uiColor: .systemGroupedBackground))
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private var backgroundColor: Color {
|
|
||||||
#if os(iOS)
|
|
||||||
return Color(uiColor: .secondarySystemGroupedBackground)
|
|
||||||
#elseif os(macOS)
|
|
||||||
return Color(nsColor: .textBackgroundColor)
|
|
||||||
#elseif os(tvOS)
|
|
||||||
return Color(uiColor: .black)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import SwiftUI
|
|||||||
@StateObject private var cardConfiguration = DashboardCardConfiguration()
|
@StateObject private var cardConfiguration = DashboardCardConfiguration()
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
@State private var showCardManagement = false
|
@State private var showCardManagement = false
|
||||||
|
#endif
|
||||||
|
#if os(tvOS)
|
||||||
@State private var showGroups = false
|
@State private var showGroups = false
|
||||||
@State private var showConnections = false
|
@State private var showConnections = false
|
||||||
@State private var buttonState = ButtonVisibilityState()
|
@State private var buttonState = ButtonVisibilityState()
|
||||||
@@ -58,22 +60,7 @@ import SwiftUI
|
|||||||
|
|
||||||
@ViewBuilder private var content: some View {
|
@ViewBuilder private var content: some View {
|
||||||
Group {
|
Group {
|
||||||
#if os(iOS)
|
overviewPage
|
||||||
if useLegacyTabView {
|
|
||||||
if Variant.screenshotMode || profile.status.isConnectedStrict {
|
|
||||||
VStack {
|
|
||||||
pageSelector
|
|
||||||
pageContent
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
overviewPage
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
overviewPage
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
overviewPage
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
@@ -110,19 +97,15 @@ import SwiftUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
.sheet(isPresented: $showGroups) {
|
.sheet(isPresented: $showCardManagement, onDismiss: {
|
||||||
groupsSheetContent
|
Task { await cardConfiguration.reload() }
|
||||||
}.sheet(isPresented: $showConnections) {
|
}, content: {
|
||||||
connectionsSheetContent
|
if #available(iOS 16.0, *) {
|
||||||
}.sheet(isPresented: $showCardManagement, onDismiss: {
|
CardManagementSheet().presentationDetents([.large]).presentationDragIndicator(.visible)
|
||||||
Task { await cardConfiguration.reload() }
|
} else {
|
||||||
}, content: {
|
CardManagementSheet()
|
||||||
if #available(iOS 16.0, *) {
|
}
|
||||||
CardManagementSheet().presentationDetents([.large]).presentationDragIndicator(.visible)
|
})
|
||||||
} else {
|
|
||||||
CardManagementSheet()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
.onAppear {
|
.onAppear {
|
||||||
@@ -149,24 +132,14 @@ import SwiftUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(tvOS)
|
||||||
.onReceive(environments.commandClient.$groups) { _ in
|
.onReceive(environments.commandClient.$groups) { _ in
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
updateButtonVisibility()
|
updateButtonVisibility()
|
||||||
#if os(iOS)
|
|
||||||
if useLegacyTabView, coordinator.selection == .groups, !buttonState.showGroupsButton {
|
|
||||||
coordinator.selection = .overview
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}.onReceive(profile.$status) { _ in
|
}.onReceive(profile.$status) { _ in
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
updateButtonVisibility()
|
updateButtonVisibility()
|
||||||
#if os(iOS)
|
|
||||||
if useLegacyTabView, coordinator.selection == .groups, !buttonState.showGroupsButton {
|
|
||||||
coordinator.selection = .overview
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}.onAppear {
|
}.onAppear {
|
||||||
updateButtonVisibility()
|
updateButtonVisibility()
|
||||||
@@ -189,58 +162,13 @@ import SwiftUI
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(tvOS)
|
||||||
private func updateButtonVisibility() {
|
private func updateButtonVisibility() {
|
||||||
buttonState.update(profile: profile, commandClient: environments.commandClient)
|
buttonState.update(profile: profile, commandClient: environments.commandClient)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS) || os(tvOS)
|
||||||
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 {
|
@ToolbarContentBuilder private var toolbar: some ToolbarContent {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
ToolbarItemGroup(placement: .topBarLeading) {
|
ToolbarItemGroup(placement: .topBarLeading) {
|
||||||
@@ -249,23 +177,10 @@ import SwiftUI
|
|||||||
#endif
|
#endif
|
||||||
ToolbarItemGroup(placement: .topBarTrailing) {
|
ToolbarItemGroup(placement: .topBarTrailing) {
|
||||||
if #available(iOS 16.0, tvOS 17.0, *) {
|
if #available(iOS 16.0, tvOS 17.0, *) {
|
||||||
#if os(iOS)
|
cardManagementButton
|
||||||
if !useLegacyTabView || coordinator.selection == .overview {
|
|
||||||
cardManagementButton
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
cardManagementButton
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
StartStopButton()
|
StartStopButton()
|
||||||
#elseif os(iOS)
|
|
||||||
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
|
||||||
} else if !useLegacyTabView || coordinator.selection != .connections {
|
|
||||||
StartStopButton()
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
StartStopButton()
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,14 +204,6 @@ import SwiftUI
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if os(iOS) || os(tvOS)
|
#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 {
|
@available(iOS 16.0, *) @ViewBuilder private var cardManagementButton: some View {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
Menu {
|
Menu {
|
||||||
|
|||||||
@@ -5,13 +5,17 @@ import SwiftUI
|
|||||||
@MainActor
|
@MainActor
|
||||||
public struct StartStopButton: View {
|
public struct StartStopButton: View {
|
||||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
|
private let showsRuntimeDuration: Bool
|
||||||
|
|
||||||
public init() {}
|
public init(showsRuntimeDuration: Bool = false) {
|
||||||
|
self.showsRuntimeDuration = showsRuntimeDuration
|
||||||
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if let profile = environments.extensionProfile {
|
if let profile = environments.extensionProfile {
|
||||||
ToggleConnectionButton().environmentObject(profile)
|
ToggleConnectionButton(showsRuntimeDuration: showsRuntimeDuration)
|
||||||
|
.environmentObject(profile)
|
||||||
} else {
|
} else {
|
||||||
Button {} label: {
|
Button {} label: {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@@ -33,6 +37,7 @@ public struct StartStopButton: View {
|
|||||||
@State private var alert: AlertState?
|
@State private var alert: AlertState?
|
||||||
@State private var currentTime = Date()
|
@State private var currentTime = Date()
|
||||||
@State private var isStarting = false
|
@State private var isStarting = false
|
||||||
|
let showsRuntimeDuration: Bool
|
||||||
|
|
||||||
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||||
|
|
||||||
@@ -44,7 +49,7 @@ public struct StartStopButton: View {
|
|||||||
} label: {
|
} label: {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
if showRuntimeDuration, profile.status.isConnectedStrict, let duration = runtimeDuration {
|
if showsRuntimeDuration, profile.status.isConnectedStrict, let duration = runtimeDuration {
|
||||||
Text(duration)
|
Text(duration)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
@@ -121,15 +126,6 @@ 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? {
|
private var runtimeDuration: String? {
|
||||||
guard let connectedDate = profile.connectedDate else { return nil }
|
guard let connectedDate = profile.connectedDate else { return nil }
|
||||||
let interval: TimeInterval
|
let interval: TimeInterval
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Library
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
public enum DashboardPage: Int, CaseIterable, Identifiable {
|
|
||||||
public var id: Self {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
case overview
|
|
||||||
case groups
|
|
||||||
case connections
|
|
||||||
}
|
|
||||||
|
|
||||||
public extension DashboardPage {
|
|
||||||
static func enabledCases() -> [DashboardPage] {
|
|
||||||
[.overview, .groups, .connections]
|
|
||||||
}
|
|
||||||
|
|
||||||
static func enabledCases(hasGroups: Bool) -> [DashboardPage] {
|
|
||||||
var cases: [DashboardPage] = [.overview]
|
|
||||||
if hasGroups {
|
|
||||||
cases.append(.groups)
|
|
||||||
}
|
|
||||||
cases.append(.connections)
|
|
||||||
return cases
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public extension DashboardPage {
|
|
||||||
var title: String {
|
|
||||||
switch self {
|
|
||||||
case .overview:
|
|
||||||
return String(localized: "Overview")
|
|
||||||
case .groups:
|
|
||||||
return String(localized: "Groups")
|
|
||||||
case .connections:
|
|
||||||
return String(localized: "Connections")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var label: some View {
|
|
||||||
switch self {
|
|
||||||
case .overview:
|
|
||||||
return Label(title, systemImage: "text.and.command.macwindow")
|
|
||||||
case .groups:
|
|
||||||
return Label(title, systemImage: "rectangle.3.group.fill")
|
|
||||||
case .connections:
|
|
||||||
return Label(title, systemImage: "list.bullet.rectangle.portrait.fill")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func contentView(_ profileList: Binding<[ProfilePreview]>, _ selectedProfileID: Binding<Int64>, _ systemProxyAvailable: Binding<Bool>, _ systemProxyEnabled: Binding<Bool>, _ cardConfiguration: DashboardCardConfiguration) -> some View {
|
|
||||||
Group {
|
|
||||||
switch self {
|
|
||||||
case .overview:
|
|
||||||
OverviewView(profileList, selectedProfileID, systemProxyAvailable, systemProxyEnabled, cardConfiguration: cardConfiguration)
|
|
||||||
case .groups:
|
|
||||||
GroupListView()
|
|
||||||
case .connections:
|
|
||||||
ConnectionListView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,6 @@ private let logger = Logger(category: "DashboardViewModel")
|
|||||||
public final class DashboardViewModel: BaseViewModel {
|
public final class DashboardViewModel: BaseViewModel {
|
||||||
@Published public var profileList: [ProfilePreview] = []
|
@Published public var profileList: [ProfilePreview] = []
|
||||||
@Published public var selectedProfileID: Int64 = 0
|
@Published public var selectedProfileID: Int64 = 0
|
||||||
@Published public var selection = DashboardPage.overview
|
|
||||||
@Published public var systemProxyAvailable = false
|
@Published public var systemProxyAvailable = false
|
||||||
@Published public var systemProxyEnabled = false
|
@Published public var systemProxyEnabled = false
|
||||||
|
|
||||||
|
|||||||
@@ -88,12 +88,6 @@ public class ExtensionProfile: ObservableObject {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private func unregister() {
|
|
||||||
if let observer {
|
|
||||||
NotificationCenter.default.removeObserver(observer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nonisolated deinit {
|
nonisolated deinit {
|
||||||
if let observer {
|
if let observer {
|
||||||
NotificationCenter.default.removeObserver(observer)
|
NotificationCenter.default.removeObserver(observer)
|
||||||
@@ -242,7 +236,9 @@ public class ExtensionProfile: ObservableObject {
|
|||||||
try await manager.saveToPreferences()
|
try await manager.saveToPreferences()
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
try LibboxNewStandaloneCommandClient()!.serviceClose()
|
try await Task.detached(priority: .userInitiated) {
|
||||||
|
try LibboxNewStandaloneCommandClient()!.serviceClose()
|
||||||
|
}.value
|
||||||
} catch {
|
} catch {
|
||||||
logger.debug("serviceClose error: \(error.localizedDescription)")
|
logger.debug("serviceClose error: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
|
|||||||
+87
-54
@@ -33,30 +33,17 @@ struct MainView: View {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 26.0, *)
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var tabViewContent: some View {
|
private var tabViewContent: some View {
|
||||||
if shouldShowBottomAccessory {
|
if shouldShowBottomAccessory {
|
||||||
baseTabView
|
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
||||||
.tabViewBottomAccessory {
|
baseTabView
|
||||||
HStack(spacing: 12) {
|
.tabViewBottomAccessory {
|
||||||
if let profile = environments.extensionProfile {
|
bottomAccessoryContent
|
||||||
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 {
|
||||||
}
|
legacyTabView
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
baseTabView
|
baseTabView
|
||||||
}
|
}
|
||||||
@@ -72,11 +59,19 @@ struct MainView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var baseTabView: some View {
|
private var baseTabView: some View {
|
||||||
|
tabView(showsBottomAccessory: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var legacyTabView: some View {
|
||||||
|
tabView(showsBottomAccessory: shouldShowBottomAccessory)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func tabView(showsBottomAccessory: Bool) -> some View {
|
||||||
TabView(selection: $selection) {
|
TabView(selection: $selection) {
|
||||||
ForEach(NavigationPage.allCases, id: \.self) { page in
|
ForEach(NavigationPage.allCases, id: \.self) { page in
|
||||||
NavigationStackCompat {
|
NavigationStackCompat {
|
||||||
page.contentView
|
tabContent(for: page, showsBottomAccessory: showsBottomAccessory)
|
||||||
.navigationTitle(page.title)
|
|
||||||
}
|
}
|
||||||
.tag(page)
|
.tag(page)
|
||||||
.tabItem { page.label }
|
.tabItem { page.label }
|
||||||
@@ -84,40 +79,78 @@ struct MainView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func tabContent(for page: NavigationPage, showsBottomAccessory: Bool) -> some View {
|
||||||
|
if showsBottomAccessory {
|
||||||
|
let content = page.contentView
|
||||||
|
.navigationTitle(page.title)
|
||||||
|
.tabViewBottomAccessoryCompat(useSystemAccessory: false) {
|
||||||
|
bottomAccessoryContent
|
||||||
|
}
|
||||||
|
tabBarBackgroundIfAvailable(content)
|
||||||
|
} else {
|
||||||
|
let content = page.contentView
|
||||||
|
.navigationTitle(page.title)
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func tabBarBackgroundIfAvailable<Content: View>(_ content: Content) -> some View {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
|
||||||
|
private var bottomAccessoryContent: some View {
|
||||||
|
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(showsRuntimeDuration: true)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.tint(.primary)
|
||||||
|
}
|
||||||
|
|
||||||
private var mainBody: some View {
|
private var mainBody: some View {
|
||||||
Group {
|
Group {
|
||||||
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
tabViewContent
|
||||||
tabViewContent
|
.onAppear {
|
||||||
.onAppear {
|
updateButtonVisibility()
|
||||||
updateButtonVisibility()
|
}
|
||||||
}
|
.onReceive(environments.commandClient.$groups) { _ in
|
||||||
.onReceive(environments.commandClient.$groups) { _ in
|
Task { @MainActor in updateButtonVisibility() }
|
||||||
Task { @MainActor in updateButtonVisibility() }
|
}
|
||||||
}
|
.onReceive(environments.commandClient.$connections) { _ in
|
||||||
.onReceive(environments.commandClient.$connections) { _ in
|
Task { @MainActor in updateButtonVisibility() }
|
||||||
Task { @MainActor in updateButtonVisibility() }
|
}
|
||||||
}
|
.onReceive(environments.commandClient.$hasAnyConnection) { _ in
|
||||||
.onReceive(environments.commandClient.$hasAnyConnection) { _ in
|
Task { @MainActor in updateButtonVisibility() }
|
||||||
Task { @MainActor in updateButtonVisibility() }
|
}
|
||||||
}
|
.onReceive(NotificationCenter.default.publisher(for: .NEVPNStatusDidChange)) { _ in
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .NEVPNStatusDidChange)) { _ in
|
Task { @MainActor in updateButtonVisibility() }
|
||||||
Task { @MainActor in updateButtonVisibility() }
|
}
|
||||||
}
|
.onReceive(environments.$extensionProfile) { _ in
|
||||||
.onReceive(environments.$extensionProfile) { _ in
|
Task { @MainActor in updateButtonVisibility() }
|
||||||
Task { @MainActor in updateButtonVisibility() }
|
}
|
||||||
}
|
.onReceive(environments.$emptyProfiles) { _ in
|
||||||
.onReceive(environments.$emptyProfiles) { _ in
|
Task { @MainActor in updateButtonVisibility() }
|
||||||
Task { @MainActor in updateButtonVisibility() }
|
}
|
||||||
}
|
.sheet(isPresented: $showGroups) {
|
||||||
.sheet(isPresented: $showGroups) {
|
GroupsSheetContent()
|
||||||
GroupsSheetContent()
|
}
|
||||||
}
|
.sheet(isPresented: $showConnections) {
|
||||||
.sheet(isPresented: $showConnections) {
|
ConnectionsSheetContent()
|
||||||
ConnectionsSheetContent()
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
baseTabView
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
environments.postReload()
|
environments.postReload()
|
||||||
|
|||||||
Reference in New Issue
Block a user