From 711bf31d209f885760dec5b09f013c151cc4d954 Mon Sep 17 00:00:00 2001 From: John Qin Date: Fri, 15 Mar 2024 01:05:07 +0800 Subject: [PATCH] feat: menubar refine pass global commandClient as Enviroment Object show clash outbound in menubar use MenuDisclosureSection to save space of Profile and Outbound --- .../Views/Dashboard/ClashModeView.swift | 10 +-- .../Views/Dashboard/OverviewView.swift | 1 + MacLibrary/MacApplication.swift | 1 + MacLibrary/MenuView.swift | 75 +++++++++++++++++-- 4 files changed, 74 insertions(+), 13 deletions(-) diff --git a/ApplicationLibrary/Views/Dashboard/ClashModeView.swift b/ApplicationLibrary/Views/Dashboard/ClashModeView.swift index 0c69415..045fe55 100644 --- a/ApplicationLibrary/Views/Dashboard/ClashModeView.swift +++ b/ApplicationLibrary/Views/Dashboard/ClashModeView.swift @@ -5,8 +5,7 @@ import SwiftUI @MainActor public struct ClashModeView: View { @Environment(\.scenePhase) private var scenePhase - @StateObject private var commandClient = CommandClient(.clashMode) - @State private var clashMode = "" + @EnvironmentObject private var commandClient: CommandClient @State private var alert: Alert? public init() {} @@ -14,9 +13,9 @@ public struct ClashModeView: View { VStack { if commandClient.clashModeList.count > 1 { Picker("", selection: Binding(get: { - clashMode + commandClient.clashMode }, set: { newMode in - clashMode = newMode + commandClient.clashMode = newMode Task { await setClashMode(newMode) } @@ -29,9 +28,6 @@ public struct ClashModeView: View { .padding([.top], 8) } } - .onReceive(commandClient.$clashMode) { newMode in - clashMode = newMode - } .padding([.leading, .trailing]) .onAppear { commandClient.connect() diff --git a/ApplicationLibrary/Views/Dashboard/OverviewView.swift b/ApplicationLibrary/Views/Dashboard/OverviewView.swift index 7258ebf..805e9f7 100644 --- a/ApplicationLibrary/Views/Dashboard/OverviewView.swift +++ b/ApplicationLibrary/Views/Dashboard/OverviewView.swift @@ -36,6 +36,7 @@ public struct OverviewView: View { if ApplicationLibrary.inPreview || profile.status.isConnected { ExtensionStatusView() ClashModeView() + .environmentObject(environments.logClient) } if profileList.isEmpty { Text("Empty profiles") diff --git a/MacLibrary/MacApplication.swift b/MacLibrary/MacApplication.swift index fe71828..ece7a4b 100644 --- a/MacLibrary/MacApplication.swift +++ b/MacLibrary/MacApplication.swift @@ -46,6 +46,7 @@ public struct MacApplication: Scene { MenuBarExtra(isInserted: $showMenuBarExtra) { MenuView(isMenuPresented: $isMenuPresented) .environmentObject(environments) + .environmentObject(environments.logClient) } label: { Image("MenuIcon") } diff --git a/MacLibrary/MenuView.swift b/MacLibrary/MenuView.swift index ee35ff5..44e72f8 100644 --- a/MacLibrary/MenuView.swift +++ b/MacLibrary/MenuView.swift @@ -11,11 +11,14 @@ public struct MenuView: View { @Environment(\.openWindow) private var openWindow private static let sliderWidth: CGFloat = 270 + public static let MenuDisclosureSectionPaddingFix: CGFloat = -14.0 @Binding private var isMenuPresented: Bool @State private var isLoading = true @State private var profile: ExtensionProfile? + + @EnvironmentObject private var commandClient: CommandClient public init(isMenuPresented: Binding) { _isMenuPresented = isMenuPresented @@ -41,6 +44,9 @@ public struct MenuView: View { if let profile { ProfilePicker(profile) } + if( commandClient.clashModeList.count > 0) { + ClashOutboundPicker() + } Divider() MenuCommand { NSApp.setActivationPolicy(.regular) @@ -118,6 +124,7 @@ public struct MenuView: View { @State private var selectedProfileID: Int64 = 0 @State private var reasserting = false @State private var alert: Alert? + @State private var isExpanded = false private var selectedProfileIDLocal: Binding { $selectedProfileID.withSetter { newValue in @@ -140,14 +147,22 @@ public struct MenuView: View { if profileList.isEmpty { Text("Empty profiles") } else { - MenuSection("Profile") - Picker("", selection: selectedProfileIDLocal) { - ForEach(profileList, id: \.id) { profile in - Text(profile.name) + Divider() + MenuDisclosureSection( + profileList.firstIndex(where: { $0.id == selectedProfileIDLocal.wrappedValue }).map { "Profile: \(profileList[$0].name)" } ?? "Profile", + divider: false, + isExpanded: $isExpanded + ) { + Picker("", selection: selectedProfileIDLocal) { + ForEach(profileList, id: \.id) { profile in + Text(profile.name).frame(maxWidth: .infinity, alignment: .leading) + } } + .pickerStyle(.inline) + .disabled(!profile.status.isSwitchable || reasserting) } - .pickerStyle(.inline) - .disabled(!profile.status.isSwitchable || reasserting) + .padding(.leading, MenuView.MenuDisclosureSectionPaddingFix) + .padding(.trailing, MenuView.MenuDisclosureSectionPaddingFix) } } } @@ -204,4 +219,52 @@ public struct MenuView: View { try LibboxNewStandaloneCommandClient()?.serviceReload() } } + + private struct ClashOutboundPicker: View { + @EnvironmentObject private var commandClient: CommandClient + @State private var isclashModeExpanded = false + @State private var alert: Alert? + + @State private var wifiSelection: MenuEntry.ID? = "Wi-Fi Art Thou Romeo" + + private var clashLists: [MenuEntry] { + commandClient.clashModeList.map { stringValue in + MenuEntry(name: stringValue, systemImage: "") + } + } + + var body: some View { + viewBuilder { + Divider() + MenuDisclosureSection("Outbound: " + commandClient.clashMode, divider: false, isExpanded: $isclashModeExpanded) { + MenuScrollView(maxHeight: 135) { + ForEach(commandClient.clashModeList, id: \.self) { it in + MenuCommand { + commandClient.clashMode = it; + } label: { + if it == commandClient.clashMode { + Image(systemName: "play.fill") + } + Text(it) + } + .padding(.leading, MenuView.MenuDisclosureSectionPaddingFix) + .padding(.trailing, MenuView.MenuDisclosureSectionPaddingFix) + } + } + } + .padding(.leading, MenuView.MenuDisclosureSectionPaddingFix) + .padding(.trailing, MenuView.MenuDisclosureSectionPaddingFix) + } + .alertBinding($alert) + } + private nonisolated func setClashMode(_ newMode: String) async { + do { + try LibboxNewStandaloneCommandClient()!.setClashMode(newMode) + } catch { + await MainActor.run { + alert = Alert(error) + } + } + } + } }