feat: improve Atlas narrow window responsiveness
This commit is contained in:
@@ -231,7 +231,8 @@ private struct SidebarRouteRow: View {
|
|||||||
Text(route.subtitle)
|
Text(route.subtitle)
|
||||||
.font(AtlasTypography.captionSmall)
|
.font(AtlasTypography.captionSmall)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.lineLimit(2)
|
.lineLimit(1)
|
||||||
|
.truncationMode(.tail)
|
||||||
}
|
}
|
||||||
} icon: {
|
} icon: {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ struct AtlasApp: App {
|
|||||||
WindowGroup(AtlasL10n.string("app.name")) {
|
WindowGroup(AtlasL10n.string("app.name")) {
|
||||||
AppShellView(model: model)
|
AppShellView(model: model)
|
||||||
.environment(\.locale, model.appLanguage.locale)
|
.environment(\.locale, model.appLanguage.locale)
|
||||||
.frame(minWidth: 1120, minHeight: 720)
|
.frame(minWidth: 940, minHeight: 640)
|
||||||
}
|
}
|
||||||
.commands {
|
.commands {
|
||||||
AtlasAppCommands(model: model)
|
AtlasAppCommands(model: model)
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ final class AtlasAppUITests: XCTestCase {
|
|||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
let stateFile = NSTemporaryDirectory() + UUID().uuidString + "/workspace-state.json"
|
let stateFile = NSTemporaryDirectory() + UUID().uuidString + "/workspace-state.json"
|
||||||
app.launchEnvironment["ATLAS_STATE_FILE"] = stateFile
|
app.launchEnvironment["ATLAS_STATE_FILE"] = stateFile
|
||||||
|
app.launchArguments += ["-ApplePersistenceIgnoreState", "YES"]
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ public enum AtlasSpacing {
|
|||||||
/// 24pt — screen-level vertical rhythm.
|
/// 24pt — screen-level vertical rhythm.
|
||||||
public static let xxl: CGFloat = 24
|
public static let xxl: CGFloat = 24
|
||||||
/// 28pt — screen horizontal margin.
|
/// 28pt — screen horizontal margin.
|
||||||
public static let screenH: CGFloat = 28
|
public static let screenH: CGFloat = 24
|
||||||
/// 32pt — large section separation.
|
/// 32pt — large section separation.
|
||||||
public static let section: CGFloat = 32
|
public static let section: CGFloat = 32
|
||||||
}
|
}
|
||||||
@@ -227,21 +227,21 @@ public enum AtlasMotion {
|
|||||||
/// Shared layout constants.
|
/// Shared layout constants.
|
||||||
public enum AtlasLayout {
|
public enum AtlasLayout {
|
||||||
/// Maximum content reading width — prevents overly long text lines.
|
/// Maximum content reading width — prevents overly long text lines.
|
||||||
public static let maxReadingWidth: CGFloat = 960
|
public static let maxReadingWidth: CGFloat = 920
|
||||||
/// Standard 3-column metric grid definition.
|
/// Standard 3-column metric grid definition.
|
||||||
public static let metricColumns: [GridItem] = [
|
public static let metricColumns: [GridItem] = [
|
||||||
GridItem(.flexible(minimum: 220), spacing: AtlasSpacing.lg),
|
GridItem(.flexible(minimum: 180), spacing: AtlasSpacing.lg),
|
||||||
GridItem(.flexible(minimum: 220), spacing: AtlasSpacing.lg),
|
GridItem(.flexible(minimum: 180), spacing: AtlasSpacing.lg),
|
||||||
GridItem(.flexible(minimum: 220), spacing: AtlasSpacing.lg),
|
GridItem(.flexible(minimum: 180), spacing: AtlasSpacing.lg),
|
||||||
]
|
]
|
||||||
/// 2-column grid for wider cards.
|
/// 2-column grid for wider cards.
|
||||||
public static let wideColumns: [GridItem] = [
|
public static let wideColumns: [GridItem] = [
|
||||||
GridItem(.flexible(minimum: 300), spacing: AtlasSpacing.lg),
|
GridItem(.flexible(minimum: 220), spacing: AtlasSpacing.lg),
|
||||||
GridItem(.flexible(minimum: 300), spacing: AtlasSpacing.lg),
|
GridItem(.flexible(minimum: 220), spacing: AtlasSpacing.lg),
|
||||||
]
|
]
|
||||||
/// Sidebar width range.
|
/// Sidebar width range.
|
||||||
public static let sidebarMinWidth: CGFloat = 230
|
public static let sidebarMinWidth: CGFloat = 180
|
||||||
public static let sidebarIdealWidth: CGFloat = 260
|
public static let sidebarIdealWidth: CGFloat = 220
|
||||||
/// Sidebar icon container size (pill-style like System Settings).
|
/// Sidebar icon container size (pill-style like System Settings).
|
||||||
public static let sidebarIconSize: CGFloat = 32
|
public static let sidebarIconSize: CGFloat = 32
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,37 +76,52 @@ public struct AtlasScreen<Content: View>: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
ZStack {
|
GeometryReader { proxy in
|
||||||
LinearGradient(
|
let horizontalPadding = resolvedHorizontalPadding(for: proxy.size.width)
|
||||||
colors: [AtlasColor.canvasTop, AtlasColor.canvasBottom],
|
|
||||||
startPoint: .top,
|
|
||||||
endPoint: .bottom
|
|
||||||
)
|
|
||||||
.ignoresSafeArea()
|
|
||||||
|
|
||||||
Group {
|
ZStack {
|
||||||
if useScrollView {
|
LinearGradient(
|
||||||
ScrollView {
|
colors: [AtlasColor.canvasTop, AtlasColor.canvasBottom],
|
||||||
contentStack
|
startPoint: .top,
|
||||||
|
endPoint: .bottom
|
||||||
|
)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
Group {
|
||||||
|
if useScrollView {
|
||||||
|
ScrollView {
|
||||||
|
contentStack(horizontalPadding: horizontalPadding)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
contentStack(horizontalPadding: horizontalPadding)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
contentStack
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var contentStack: some View {
|
private func contentStack(horizontalPadding: CGFloat) -> some View {
|
||||||
VStack(alignment: .leading, spacing: AtlasSpacing.xxl) {
|
VStack(alignment: .leading, spacing: AtlasSpacing.xxl) {
|
||||||
header
|
header
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
.frame(maxWidth: AtlasLayout.maxReadingWidth, maxHeight: .infinity, alignment: .topLeading)
|
.frame(maxWidth: AtlasLayout.maxReadingWidth, maxHeight: .infinity, alignment: .topLeading)
|
||||||
.padding(.horizontal, AtlasSpacing.screenH)
|
.padding(.horizontal, horizontalPadding)
|
||||||
.padding(.vertical, AtlasSpacing.xxl)
|
.padding(.vertical, AtlasSpacing.xxl)
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func resolvedHorizontalPadding(for width: CGFloat) -> CGFloat {
|
||||||
|
switch width {
|
||||||
|
case ..<820:
|
||||||
|
return 16
|
||||||
|
case ..<980:
|
||||||
|
return 20
|
||||||
|
default:
|
||||||
|
return AtlasSpacing.screenH
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var header: some View {
|
private var header: some View {
|
||||||
VStack(alignment: .leading, spacing: AtlasSpacing.sm) {
|
VStack(alignment: .leading, spacing: AtlasSpacing.sm) {
|
||||||
Text(title)
|
Text(title)
|
||||||
|
|||||||
@@ -105,8 +105,8 @@ public struct AppsFeatureView: View {
|
|||||||
tone: selectedAppMatchingPreview == nil ? .neutral : .warning
|
tone: selectedAppMatchingPreview == nil ? .neutral : .warning
|
||||||
) {
|
) {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
let isWide = proxy.size.width >= 760
|
let isWide = proxy.size.width >= 680
|
||||||
let sidebarWidth = min(max(proxy.size.width * 0.32, 260), 300)
|
let sidebarWidth = min(max(proxy.size.width * 0.3, 220), 280)
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
if isWide {
|
if isWide {
|
||||||
@@ -455,36 +455,50 @@ private struct AppDetailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: AtlasSpacing.md) {
|
ViewThatFits(in: .horizontal) {
|
||||||
Group {
|
HStack(alignment: .center, spacing: AtlasSpacing.md) {
|
||||||
if previewPlan == nil {
|
previewButton
|
||||||
Button(isBuildingPreview ? AtlasL10n.string("apps.preview.running") : AtlasL10n.string("apps.preview.action")) {
|
uninstallButton
|
||||||
onPreview()
|
|
||||||
}
|
|
||||||
.buttonStyle(.atlasPrimary)
|
|
||||||
} else {
|
|
||||||
Button(isBuildingPreview ? AtlasL10n.string("apps.preview.running") : AtlasL10n.string("apps.preview.action")) {
|
|
||||||
onPreview()
|
|
||||||
}
|
|
||||||
.buttonStyle(.atlasSecondary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.disabled(isBusy)
|
|
||||||
.accessibilityIdentifier("apps.preview.\(app.id.uuidString)")
|
|
||||||
.accessibilityHint(AtlasL10n.string("apps.preview.hint"))
|
|
||||||
|
|
||||||
Button(isUninstalling ? AtlasL10n.string("apps.uninstall.running") : AtlasL10n.string("apps.uninstall.action")) {
|
VStack(alignment: .leading, spacing: AtlasSpacing.md) {
|
||||||
onUninstall()
|
previewButton
|
||||||
|
uninstallButton
|
||||||
}
|
}
|
||||||
.buttonStyle(.atlasPrimary)
|
|
||||||
.disabled(isBusy || previewPlan == nil)
|
|
||||||
.accessibilityIdentifier("apps.uninstall.\(app.id.uuidString)")
|
|
||||||
.accessibilityHint(AtlasL10n.string("apps.uninstall.hint"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var previewButton: some View {
|
||||||
|
Group {
|
||||||
|
if previewPlan == nil {
|
||||||
|
Button(isBuildingPreview ? AtlasL10n.string("apps.preview.running") : AtlasL10n.string("apps.preview.action")) {
|
||||||
|
onPreview()
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasPrimary)
|
||||||
|
} else {
|
||||||
|
Button(isBuildingPreview ? AtlasL10n.string("apps.preview.running") : AtlasL10n.string("apps.preview.action")) {
|
||||||
|
onPreview()
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasSecondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(isBusy)
|
||||||
|
.accessibilityIdentifier("apps.preview.\(app.id.uuidString)")
|
||||||
|
.accessibilityHint(AtlasL10n.string("apps.preview.hint"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private var uninstallButton: some View {
|
||||||
|
Button(isUninstalling ? AtlasL10n.string("apps.uninstall.running") : AtlasL10n.string("apps.uninstall.action")) {
|
||||||
|
onUninstall()
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasPrimary)
|
||||||
|
.disabled(isBusy || previewPlan == nil)
|
||||||
|
.accessibilityIdentifier("apps.uninstall.\(app.id.uuidString)")
|
||||||
|
.accessibilityHint(AtlasL10n.string("apps.uninstall.hint"))
|
||||||
|
}
|
||||||
|
|
||||||
private func icon(for kind: ActionItem.Kind) -> String {
|
private func icon(for kind: ActionItem.Kind) -> String {
|
||||||
switch kind {
|
switch kind {
|
||||||
case .removeCache:
|
case .removeCache:
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ public struct HistoryFeatureView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
let isWide = proxy.size.width >= 760
|
let isWide = proxy.size.width >= 680
|
||||||
let sidebarWidth = min(max(proxy.size.width * 0.32, 250), 290)
|
let sidebarWidth = min(max(proxy.size.width * 0.3, 210), 270)
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
if isWide {
|
if isWide {
|
||||||
|
|||||||
@@ -82,20 +82,15 @@ public struct PermissionsFeatureView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: AtlasSpacing.md) {
|
ViewThatFits(in: .horizontal) {
|
||||||
if let nextActionKind {
|
HStack(alignment: .center, spacing: AtlasSpacing.md) {
|
||||||
Button(buttonTitle(for: nextActionKind)) {
|
nextStepButtons
|
||||||
performAction(for: nextActionKind)
|
Spacer(minLength: 0)
|
||||||
}
|
|
||||||
.buttonStyle(.atlasPrimary)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(action: onRefresh) {
|
VStack(alignment: .leading, spacing: AtlasSpacing.md) {
|
||||||
Label(AtlasL10n.string("permissions.refresh"), systemImage: "arrow.clockwise")
|
nextStepButtons
|
||||||
}
|
}
|
||||||
.buttonStyle(.atlasSecondary)
|
|
||||||
.accessibilityIdentifier("permissions.refresh")
|
|
||||||
.accessibilityHint(AtlasL10n.string("permissions.refresh.hint"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,6 +219,23 @@ public struct PermissionsFeatureView: View {
|
|||||||
return nextActionKind.systemImage
|
return nextActionKind.systemImage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var nextStepButtons: some View {
|
||||||
|
if let nextActionKind {
|
||||||
|
Button(buttonTitle(for: nextActionKind)) {
|
||||||
|
performAction(for: nextActionKind)
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasPrimary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(action: onRefresh) {
|
||||||
|
Label(AtlasL10n.string("permissions.refresh"), systemImage: "arrow.clockwise")
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasSecondary)
|
||||||
|
.accessibilityIdentifier("permissions.refresh")
|
||||||
|
.accessibilityHint(AtlasL10n.string("permissions.refresh.hint"))
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func permissionRow(_ state: PermissionState) -> some View {
|
private func permissionRow(_ state: PermissionState) -> some View {
|
||||||
AtlasDetailRow(
|
AtlasDetailRow(
|
||||||
|
|||||||
@@ -40,22 +40,24 @@ public struct SettingsFeatureView: View {
|
|||||||
subtitle: AtlasL10n.string("settings.panel.subtitle")
|
subtitle: AtlasL10n.string("settings.panel.subtitle")
|
||||||
) {
|
) {
|
||||||
VStack(alignment: .leading, spacing: AtlasSpacing.xl) {
|
VStack(alignment: .leading, spacing: AtlasSpacing.xl) {
|
||||||
HStack(spacing: AtlasSpacing.sm) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
ForEach(SettingsPanel.allCases) { panel in
|
HStack(spacing: AtlasSpacing.sm) {
|
||||||
Group {
|
ForEach(SettingsPanel.allCases) { panel in
|
||||||
if selectedPanel == panel {
|
Group {
|
||||||
Button(panel.title) {
|
if selectedPanel == panel {
|
||||||
selectedPanel = panel
|
Button(panel.title) {
|
||||||
|
selectedPanel = panel
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasSecondary)
|
||||||
|
} else {
|
||||||
|
Button(panel.title) {
|
||||||
|
selectedPanel = panel
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasGhost)
|
||||||
}
|
}
|
||||||
.buttonStyle(.atlasSecondary)
|
|
||||||
} else {
|
|
||||||
Button(panel.title) {
|
|
||||||
selectedPanel = panel
|
|
||||||
}
|
|
||||||
.buttonStyle(.atlasGhost)
|
|
||||||
}
|
}
|
||||||
|
.accessibilityIdentifier("settings.panel.\(panel.id)")
|
||||||
}
|
}
|
||||||
.accessibilityIdentifier("settings.panel.\(panel.id)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,21 +239,32 @@ public struct SettingsFeatureView: View {
|
|||||||
.font(AtlasTypography.body)
|
.font(AtlasTypography.body)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: AtlasSpacing.md) {
|
ViewThatFits(in: .horizontal) {
|
||||||
Button(AtlasL10n.string("settings.trust.documents.ack")) {
|
HStack(alignment: .center, spacing: AtlasSpacing.md) {
|
||||||
presentedDocument = .acknowledgement(settings.acknowledgementText)
|
trustDocumentButtons
|
||||||
}
|
}
|
||||||
.buttonStyle(.atlasSecondary)
|
|
||||||
|
|
||||||
Button(AtlasL10n.string("settings.trust.documents.notices")) {
|
VStack(alignment: .leading, spacing: AtlasSpacing.md) {
|
||||||
presentedDocument = .notices(settings.thirdPartyNoticesText)
|
trustDocumentButtons
|
||||||
}
|
}
|
||||||
.buttonStyle(.atlasSecondary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var trustDocumentButtons: some View {
|
||||||
|
Button(AtlasL10n.string("settings.trust.documents.ack")) {
|
||||||
|
presentedDocument = .acknowledgement(settings.acknowledgementText)
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasSecondary)
|
||||||
|
|
||||||
|
Button(AtlasL10n.string("settings.trust.documents.notices")) {
|
||||||
|
presentedDocument = .notices(settings.thirdPartyNoticesText)
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasSecondary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum SettingsPanel: String, CaseIterable, Identifiable {
|
private enum SettingsPanel: String, CaseIterable, Identifiable {
|
||||||
|
|||||||
@@ -86,38 +86,20 @@ public struct SmartCleanFeatureView: View {
|
|||||||
systemImage: primaryAction.systemImage
|
systemImage: primaryAction.systemImage
|
||||||
)
|
)
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: AtlasSpacing.md) {
|
ViewThatFits(in: .horizontal) {
|
||||||
Button(action: primaryAction.handler(startScan: onStartScan, refreshPreview: onRefreshPreview, executePlan: onExecutePlan)) {
|
HStack(alignment: .center, spacing: AtlasSpacing.md) {
|
||||||
Label(primaryAction.buttonTitle, systemImage: primaryAction.buttonSystemImage)
|
primaryActionButton
|
||||||
|
supportingActionButtons
|
||||||
|
Spacer(minLength: 0)
|
||||||
}
|
}
|
||||||
.buttonStyle(.atlasPrimary)
|
|
||||||
.keyboardShortcut(.defaultAction)
|
|
||||||
.disabled(primaryAction.isDisabled(canExecutePlan: canExecutePlan))
|
|
||||||
.accessibilityIdentifier(primaryAction.accessibilityIdentifier)
|
|
||||||
.accessibilityHint(primaryAction.accessibilityHint)
|
|
||||||
|
|
||||||
if primaryAction != .scan {
|
VStack(alignment: .leading, spacing: AtlasSpacing.md) {
|
||||||
Button(action: onStartScan) {
|
primaryActionButton
|
||||||
Label(AtlasL10n.string("smartclean.action.runScan"), systemImage: "sparkles")
|
HStack(alignment: .center, spacing: AtlasSpacing.md) {
|
||||||
|
supportingActionButtons
|
||||||
|
Spacer(minLength: 0)
|
||||||
}
|
}
|
||||||
.buttonStyle(.atlasSecondary)
|
|
||||||
.keyboardShortcut("s", modifiers: [.command, .option])
|
|
||||||
.disabled(isScanning || isExecutingPlan)
|
|
||||||
.accessibilityIdentifier("smartclean.runScan")
|
|
||||||
.accessibilityHint(AtlasL10n.string("smartclean.action.runScan.hint"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if primaryAction != .refresh, !findings.isEmpty {
|
|
||||||
Button(action: onRefreshPreview) {
|
|
||||||
Label(AtlasL10n.string("smartclean.action.refreshPreview"), systemImage: "arrow.clockwise")
|
|
||||||
}
|
|
||||||
.buttonStyle(.atlasGhost)
|
|
||||||
.disabled(isScanning || isExecutingPlan)
|
|
||||||
.accessibilityIdentifier("smartclean.refreshPreview")
|
|
||||||
.accessibilityHint(AtlasL10n.string("smartclean.action.refreshPreview.hint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(minLength: 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,6 +381,41 @@ public struct SmartCleanFeatureView: View {
|
|||||||
return .refresh
|
return .refresh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var primaryActionButton: some View {
|
||||||
|
Button(action: primaryAction.handler(startScan: onStartScan, refreshPreview: onRefreshPreview, executePlan: onExecutePlan)) {
|
||||||
|
Label(primaryAction.buttonTitle, systemImage: primaryAction.buttonSystemImage)
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasPrimary)
|
||||||
|
.keyboardShortcut(.defaultAction)
|
||||||
|
.disabled(primaryAction.isDisabled(canExecutePlan: canExecutePlan))
|
||||||
|
.accessibilityIdentifier(primaryAction.accessibilityIdentifier)
|
||||||
|
.accessibilityHint(primaryAction.accessibilityHint)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var supportingActionButtons: some View {
|
||||||
|
if primaryAction != .scan {
|
||||||
|
Button(action: onStartScan) {
|
||||||
|
Label(AtlasL10n.string("smartclean.action.runScan"), systemImage: "sparkles")
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasSecondary)
|
||||||
|
.keyboardShortcut("s", modifiers: [.command, .option])
|
||||||
|
.disabled(isScanning || isExecutingPlan)
|
||||||
|
.accessibilityIdentifier("smartclean.runScan")
|
||||||
|
.accessibilityHint(AtlasL10n.string("smartclean.action.runScan.hint"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if primaryAction != .refresh, !findings.isEmpty {
|
||||||
|
Button(action: onRefreshPreview) {
|
||||||
|
Label(AtlasL10n.string("smartclean.action.refreshPreview"), systemImage: "arrow.clockwise")
|
||||||
|
}
|
||||||
|
.buttonStyle(.atlasGhost)
|
||||||
|
.disabled(isScanning || isExecutingPlan)
|
||||||
|
.accessibilityIdentifier("smartclean.refreshPreview")
|
||||||
|
.accessibilityHint(AtlasL10n.string("smartclean.action.refreshPreview.hint"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func supportText(for kind: ActionItem.Kind) -> String {
|
private func supportText(for kind: ActionItem.Kind) -> String {
|
||||||
switch kind {
|
switch kind {
|
||||||
case .removeCache:
|
case .removeCache:
|
||||||
|
|||||||
Reference in New Issue
Block a user