Fix clash mode card style
This commit is contained in:
@@ -6,6 +6,11 @@ public struct ClashModeCard: View {
|
||||
@EnvironmentObject private var commandClient: CommandClient
|
||||
@State private var clashMode: String = ""
|
||||
@State private var alert: AlertState?
|
||||
#if !os(tvOS)
|
||||
@Namespace private var tabNamespace
|
||||
@State private var availableWidth: CGFloat = 0
|
||||
@State private var tabBarWidth: CGFloat = 0
|
||||
#endif
|
||||
|
||||
public init() {}
|
||||
|
||||
@@ -14,8 +19,37 @@ public struct ClashModeCard: View {
|
||||
DashboardCardView(title: "", isHalfWidth: false) {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
DashboardCardHeader(icon: "circle.grid.2x2.fill", title: "Mode")
|
||||
modeMenu
|
||||
#if os(tvOS)
|
||||
modeMenu
|
||||
#else
|
||||
if tabsFit {
|
||||
modeTabBar
|
||||
} else {
|
||||
modeMenu
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if !os(tvOS)
|
||||
.background(
|
||||
GeometryReader { geo in
|
||||
Color.clear.preference(key: AvailableWidthKey.self, value: geo.size.width)
|
||||
}
|
||||
)
|
||||
.onPreferenceChange(AvailableWidthKey.self) { newValue in
|
||||
availableWidth = newValue
|
||||
}
|
||||
.background(
|
||||
tabBarContent
|
||||
.fixedSize()
|
||||
.hidden()
|
||||
.background(GeometryReader { geo in
|
||||
Color.clear.preference(key: TabBarWidthKey.self, value: geo.size.width)
|
||||
})
|
||||
)
|
||||
.onPreferenceChange(TabBarWidthKey.self) { newValue in
|
||||
tabBarWidth = newValue
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.onAppear {
|
||||
clashMode = commandClient.clashMode
|
||||
@@ -64,6 +98,62 @@ public struct ClashModeCard: View {
|
||||
.selectorBackground()
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
private var modeTabBar: some View {
|
||||
HStack(spacing: 0) {
|
||||
ForEach(commandClient.clashModeList, id: \.self) { mode in
|
||||
Button {
|
||||
withAnimation {
|
||||
clashMode = mode
|
||||
}
|
||||
Task {
|
||||
await setClashMode(mode)
|
||||
}
|
||||
} label: {
|
||||
Text(mode)
|
||||
.font(.system(size: buttonFontSize, weight: .medium))
|
||||
.foregroundStyle(mode == clashMode ? .primary : .secondary)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: buttonHeight)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.background {
|
||||
if mode == clashMode {
|
||||
selectorCapsule
|
||||
.matchedGeometryEffect(id: "selector", in: tabNamespace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.selectorBackground()
|
||||
}
|
||||
|
||||
private var tabBarContent: some View {
|
||||
HStack(spacing: 0) {
|
||||
ForEach(commandClient.clashModeList, id: \.self) { mode in
|
||||
Text(mode)
|
||||
.font(.system(size: buttonFontSize, weight: .medium))
|
||||
.lineLimit(1)
|
||||
.padding(.horizontal, 14)
|
||||
.frame(height: buttonHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var selectorCapsule: some View {
|
||||
if #available(iOS 26.0, macOS 26.0, *) {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(.ultraThinMaterial)
|
||||
} else {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color.primary.opacity(0.1))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private var buttonHeight: CGFloat {
|
||||
#if os(tvOS)
|
||||
60
|
||||
@@ -90,6 +180,12 @@ public struct ClashModeCard: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
private var tabsFit: Bool {
|
||||
tabBarWidth > 0 && tabBarWidth <= availableWidth
|
||||
}
|
||||
#endif
|
||||
|
||||
private var shouldShowPicker: Bool {
|
||||
commandClient.clashModeList.count > 1
|
||||
}
|
||||
@@ -104,3 +200,19 @@ public struct ClashModeCard: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
private struct AvailableWidthKey: PreferenceKey {
|
||||
static var defaultValue: CGFloat = 0
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
value = max(value, nextValue())
|
||||
}
|
||||
}
|
||||
|
||||
private struct TabBarWidthKey: PreferenceKey {
|
||||
static var defaultValue: CGFloat = 0
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
value = max(value, nextValue())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user