Redesign dashboard
This commit is contained in:
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user