Redesign dashboard

This commit is contained in:
世界
2025-11-26 22:25:51 +08:00
parent b7a810b168
commit 68b4142340
17 changed files with 759 additions and 89 deletions
@@ -14,6 +14,9 @@ public struct OverviewView: View {
@Binding private var systemProxyEnabled: Bool
@StateObject private var viewModel = OverviewViewModel()
@State private var enabledCards: [DashboardCard] = []
@State private var cardOrder: [DashboardCard] = []
private var selectedProfileIDLocal: Binding<Int64> {
$selectedProfileID.withSetter { newValue in
viewModel.reasserting = true
@@ -31,56 +34,115 @@ public struct OverviewView: View {
}
public var body: some View {
VStack {
if ApplicationLibrary.inPreview || profile.status.isConnected {
ExtensionStatusView()
.environmentObject(environments.commandClient)
ClashModeView()
}
Group {
if profileList.isEmpty {
Text("Empty profiles")
} else {
FormView {
#if os(iOS) || os(tvOS)
StartStopButton()
if ApplicationLibrary.inPreview || profile.status.isConnectedStrict, systemProxyAvailable {
Toggle("HTTP Proxy", isOn: $systemProxyEnabled)
.onChangeCompat(of: systemProxyEnabled) { newValue in
Task {
await viewModel.setSystemProxyEnabled(newValue, profile: profile)
}
}
}
Section("Profile") {
Picker(selection: selectedProfileIDLocal) {
ForEach(profileList, id: \.id) { profile in
Text(profile.name).tag(profile.id)
}
} label: {}
.pickerStyle(.inline)
}
#elseif os(macOS)
if ApplicationLibrary.inPreview || profile.status.isConnectedStrict, systemProxyAvailable {
Toggle("HTTP Proxy", isOn: $systemProxyEnabled)
.onChangeCompat(of: systemProxyEnabled) { newValue in
Task {
await viewModel.setSystemProxyEnabled(newValue, profile: profile)
}
}
}
Section("Profile") {
ForEach(profileList, id: \.id) { profile in
Picker(profile.name, selection: selectedProfileIDLocal) {
Text("").tag(profile.id)
}
}
.pickerStyle(.radioGroup)
}
#endif
VStack {
Spacer()
Text("Empty profiles")
.foregroundStyle(.secondary)
Spacer()
}
} else {
ScrollView {
cardGrid
.padding()
}
}
}
.onAppear {
Task {
enabledCards = await viewModel.loadEnabledCards()
cardOrder = await viewModel.loadCardOrder()
}
}
.alertBinding($viewModel.alert)
.disabled(!ApplicationLibrary.inPreview && (!profile.status.isSwitchable || viewModel.reasserting))
}
@ViewBuilder
private var cardGrid: some View {
let orderedCards = viewModel.getOrderedEnabledCards(enabledCards: enabledCards, order: cardOrder)
let visibleCards = orderedCards.filter { shouldShowCard($0) }
let groupedCards = groupCards(visibleCards)
VStack(spacing: 16) {
ForEach(Array(groupedCards.enumerated()), id: \.offset) { _, group in
if group.count == 2 {
HStack(spacing: 16) {
cardView(for: group[0])
.frame(maxWidth: .infinity)
cardView(for: group[1])
.frame(maxWidth: .infinity)
}
} else {
cardView(for: group[0])
}
}
}
}
private func groupCards(_ cards: [DashboardCard]) -> [[DashboardCard]] {
var result: [[DashboardCard]] = []
var index = 0
while index < cards.count {
let card = cards[index]
if card.isHalfWidth, index + 1 < cards.count, cards[index + 1].isHalfWidth {
result.append([card, cards[index + 1]])
index += 2
} else {
result.append([card])
index += 1
}
}
return result
}
private func shouldShowCard(_ card: DashboardCard) -> Bool {
switch card {
case .status, .connections, .traffic, .trafficTotal:
return ApplicationLibrary.inPreview || profile.status.isConnected
case .httpProxy:
return (ApplicationLibrary.inPreview || profile.status.isConnectedStrict) && systemProxyAvailable
case .clashMode:
return ApplicationLibrary.inPreview || profile.status.isConnected
case .profile:
return true
}
}
@ViewBuilder
private func cardView(for card: DashboardCard) -> some View {
switch card {
case .status:
StatusCard()
.environmentObject(environments.commandClient)
case .connections:
ConnectionsCard()
.environmentObject(environments.commandClient)
case .traffic:
TrafficCard()
.environmentObject(environments.commandClient)
case .trafficTotal:
TrafficTotalCard()
.environmentObject(environments.commandClient)
case .httpProxy:
HTTPProxyCard(
systemProxyAvailable: $systemProxyAvailable,
systemProxyEnabled: $systemProxyEnabled
) { newValue in
await viewModel.setSystemProxyEnabled(newValue, profile: profile)
}
case .clashMode:
ClashModeCard()
.environmentObject(environments.commandClient)
case .profile:
ProfileCard(
profileList: $profileList,
selectedProfileID: selectedProfileIDLocal
)
}
}
}