Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 59 additions & 21 deletions App/Views/ServiceToggleView.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import SwiftUI
import AppKit
import SwiftUI

struct ServiceToggleView: View {
let config: ServiceConfig
@State private var isServiceActivated = false
@State private var isHovering = false

// MARK: Environment
@Environment(\.colorScheme) private var colorScheme
Expand All @@ -14,19 +15,19 @@ struct ServiceToggleView: View {
private let imagePadding: CGFloat = 5

var body: some View {
HStack {
Button(action: {
config.binding.wrappedValue.toggle()
if config.binding.wrappedValue && !isServiceActivated {
Task {
do {
try await config.service.activate()
} catch {
config.binding.wrappedValue = false
}
Button(action: {
config.binding.wrappedValue.toggle()
if config.binding.wrappedValue && !isServiceActivated {
Task {
do {
try await config.service.activate()
} catch {
config.binding.wrappedValue = false
}
}
}) {
}
}) {
HStack {
Circle()
.fill(buttonBackgroundColor)
.overlay(
Expand All @@ -36,18 +37,45 @@ struct ServiceToggleView: View {
.foregroundColor(buttonForegroundColor)
.padding(imagePadding)
)
.frame(width: buttonSize, height: buttonSize)
.animation(.snappy, value: config.binding.wrappedValue || isEnabled)
}
.buttonStyle(PlainButtonStyle())
.disabled(!isEnabled)
.frame(width: buttonSize, height: buttonSize)

Text(config.name)
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundColor(isEnabled ? Color.primary : .primary.opacity(0.5))
Text(config.name)
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundColor(isEnabled ? Color.primary : .primary.opacity(0.5))

// Display-only: clicks pass through so the whole row stays one control.
Toggle("", isOn: config.binding)
.toggleStyle(.switch)
.controlSize(.mini)
.labelsHidden()
.allowsHitTesting(false)
.opacity(isEnabled ? 1.0 : 0.4)
.accessibilityHidden(true)
}
.padding(.horizontal, 14)
.frame(height: buttonSize)
.contentShape(Rectangle())
}
.frame(height: buttonSize)
.padding(.horizontal, 14)
.buttonStyle(ServiceToggleRowStyle())
.disabled(!isEnabled)
.background(
RoundedRectangle(cornerRadius: 6)
.fill(isHovering && isEnabled ? Color.primary.opacity(0.07) : Color.clear)
)
.onHover { hovering in
isHovering = hovering && isEnabled
}
.onChange(of: isEnabled) { _, enabled in
if !enabled {
isHovering = false
}
}
.animation(.easeOut(duration: 0.12), value: isHovering)
.pointerStyle(isEnabled ? .link : .default)
.help("\(config.binding.wrappedValue ? "Disable" : "Enable") \(config.name)")
.accessibilityLabel(config.name)
.accessibilityValue(config.binding.wrappedValue ? "Enabled" : "Disabled")
.task {
isServiceActivated = await config.isActivated
}
Expand All @@ -70,3 +98,13 @@ struct ServiceToggleView: View {
}
}
}

// Press feedback for the whole row: a slight scale-down while the mouse is held.
private struct ServiceToggleRowStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
.opacity(configuration.isPressed ? 0.85 : 1.0)
.animation(.easeOut(duration: 0.1), value: configuration.isPressed)
}
}